// Learn TypeScript:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/typescript.html
// Learn Attribute:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
//  - https://docs.cocos.com/creator/2.4/manual/en/scripting/life-cycle-callbacks.html

import { NoticeSettlement, FrameInputData, GameFrameData, FrameState, PlayerFrameState, GameInputFrame, GameEventData, BlockAnimationState, BrickPartyBlock, PlayerPositions, PositionData, ClientInputFrame, NoticeStartGame, RoomUser } from "../bean/GameBean";
import { BlockAnimationManager } from "./BlockAnimationManager";
import { SimpleGameEventHandler } from "./SimpleGameEventHandler";
import { GameMgr } from "../common/GameMgr";
import { EventType } from "../common/EventCenter";
import { GameEventHandler } from "./GameEventHandler";
import GamePlayerController from "../pfb/GamePlayerController";

// 远程玩家状态数据
interface RemotePlayerState {
    position: cc.Vec2;
    velocity: cc.Vec2;
    timestamp: number;
}

// 远程玩家缓冲区
interface RemotePlayerBuffer {
    userId: string;
    states: RemotePlayerState[];
    lastUpdateTime: number;
}

// 逻辑状态数据 - 用于渲染插值
interface LogicState {
    position: cc.Vec2;
    velocity: cc.Vec2;
    timestamp: number; // 逻辑帧时间戳
    logicFrame: number; // 逻辑帧号
}

// 渲染状态数据 - 当前渲染显示的状态
interface RenderState {
    position: cc.Vec2;
    velocity: cc.Vec2;
    lastLogicFrame: number; // 上次更新的逻辑帧
}

import { GlobalBean } from "../bean/GlobalBean";
import LeaveDialogController from "../hall/LeaveDialogController";
import { AudioManager } from "../util/AudioManager";
import { AudioMgr } from "../util/AudioMgr";
import GlobalManagerController, { getGlobalManager } from "../GlobalManagerController";
import { Config } from "../util/Config";
import { Tools } from "../util/Tools";
import CongratsDialogController from "./CongratsDialogController";
import GameLayoutController from "./GameLayoutController";
import VirtualJoystickController, { JoystickInputData } from "./VirtualJoystickController";
import { WebSocketManager } from "../net/WebSocketManager";
import { MessageId } from "../net/MessageId";
import MenuLayoutController from "../MenuLayoutController";
import InfoDialogController from "../hall/InfoDialogController";

const { ccclass, property } = cc._decorator;

@ccclass
export default class GamePageController extends cc.Component {

    // 【关键常量】逻辑帧时间，与服务器保持一致，避免位置预测误差
    private static readonly LOGIC_FRAME_DT = 0.01; // 10ms

    @property(cc.Node)
    boardBtnBack: cc.Node = null //返回按钮

    // 新增：强制结束游戏按钮（在编辑器中拖拽绑定该节点以启用）
    @property(cc.Node)
    forceCloseBtn: cc.Node = null;

    @property(LeaveDialogController)
    leaveDialogController: LeaveDialogController = null // 退出游戏弹窗

    @property(CongratsDialogController)
    congratsDialogController: CongratsDialogController = null //结算弹窗

    @property(GameLayoutController)
    gameLayoutController: GameLayoutController = null // 游戏布局控制器

    @property(MenuLayoutController)
    menuLayoutController: MenuLayoutController = null  //菜单按钮

    @property(InfoDialogController)
    infoDialogController: InfoDialogController = null // 游戏介绍弹窗

    // ========== 坐标显示测试功能 ==========
    @property(cc.Label)
    coordinateLabel: cc.Label = null // 坐标显示标签

    // ========== 摇杆控制相关 ==========
    @property(VirtualJoystickController)
    joystickController: VirtualJoystickController = null; // 摇杆控制器

    @property(cc.Node)
    playerNode: cc.Node = null; // 玩家节点

    // 统一动物预制体（包含骨骼动画）
    @property(cc.Prefab)
    animalPrefab: cc.Prefab = null; // 统一动物预制体，通过骨骼动画切换不同动物

    // ========== 倒计时系统相关 ==========
    @property(cc.Node)
    countdownNode: cc.Node = null; // 倒计时节点（包含骨骼动画组件）

    // ========== 回合数显示相关 ==========
    @property(cc.Node)
    roundDisplayNode: cc.Node = null; // 回合数显示节点

    // ========== 难度升级展示相关 ==========
    @property(cc.Node)
    jingbaoNode: cc.Node = null; // 警报节点（难度升级动画）

    // ========== Game Over 显示相关 ==========
    @property(cc.Node)
    gameoverNode: cc.Node = null; // 自己死亡后显示的GameOver节点

    @property(cc.Prefab)
    deathEffectPrefab: cc.Prefab = null; // 死亡特效预制体

    // ========== 调试信息显示 ==========
    @property(cc.Label)
    debugInfoLabel: cc.Label = null; // 调试信息标签（中文参数）

    // ========== 网络状态显示节点 ==========
    @property(cc.Node)
    net: cc.Node = null; // 顶部网络信号图标

    @property(cc.Node)
    Network: cc.Node = null; // 网络状态横幅/额外提示

    // ========== 玩家头像预制体相关 ==========
    @property([GamePlayerController])
    playerAvatarControllers: GamePlayerController[] = []; // 玩家头像预制体控制器数组

    // 方块相关
    @property(cc.Node)
    blockParent: cc.Node = null; // 方块父节点（包含现有方块）

    @property(cc.Node)
    particle: cc.Node = null;

    @property(cc.Node)
    particle1: cc.Node = null;
    @property({
        displayName: "玩家移动速度",
        tooltip: "玩家移动的最大速度（像素/秒）"
    })
    playerMoveSpeed: number = 400;

    @property({
        displayName: "移动速度缩放",
        tooltip: "移动速度的缩放因子，用于微调移动速度"
    })


    infoClick: Function = null; // 游戏介绍弹窗点击事件

    moveSpeedScale: number = 0.3;

    isLeaveGameDialogShow: boolean = false;  //是否显示退出游戏的弹窗
    isCongratsDialog: boolean = false;  //是否显示结算的弹窗

    // 摇杆控制相关状态
    private currentPlayerVelocity: cc.Vec2 = cc.Vec2.ZERO;
    private isPlayerMoving: boolean = false;

    // 玩家动物相关
    private myPlayerData: RoomUser = null; // 自己的玩家数据
    private myAnimalNode: cc.Node = null; // 自己的动物节点

    // 所有玩家动物管理
    private allPlayerAnimals: Map<string, cc.Node> = new Map(); // 所有玩家的动物节点 <userId, animalNode>
    private allPlayerData: Map<string, RoomUser> = new Map(); // 所有玩家的数据 <userId, PlayerData>

    // ========== 帧同步相关变量 ==========
    private clientFrameId: number = 0; // 客户端帧ID计数器
    private frameInputBuffer: GameInputFrame[] = []; // 当前批次的输入缓冲区
    private lastBatchSendTime: number = 0; // 上次发送批量数据的时间
    private batchSendInterval: number = 50; // 50ms发送间隔

    // ========== 精度控制常量 ==========
    private readonly PRECISION_DECIMALS: number = 6; // 游戏精度：小数点后6位

    // 网络延迟补偿和时间同步
    private serverTimeOffset: number = 0; // 服务器时间偏移
    private networkLatency: number = 0; // 网络延迟（毫秒）
    private lastPingTime: number = 0; // 上次ping时间

    // 预测-回滚-确认相关
    private frameHistory: Map<number, FrameState> = new Map(); // 帧历史记录
    private confirmedFrameId: number = 0; // 最新确认的帧ID
    private maxHistoryFrames: number = 60; // 最多保存3秒的历史（60帧）
    private predictionBuffer: number = 3; // 预测缓冲帧数


    // 朝向控制相关（度/秒），从原来以60FPS为基准的数值改为真实“度/秒”语义
    @property({
        displayName: "旋转速度(度/秒)",
        tooltip: "动物朝向旋转速度，单位: 度/秒。可在不同机型上微调手感"
    })
    private rotationSmoothSpeed: number = 400.0;
    private lastMovementDirection: cc.Vec2 = cc.Vec2.ZERO; // 上次移动方向

    // 锁步帧同步系统 - 调整为100FPS以确保50ms内产生5帧
    private logicFrameRate: number = 100; // 逻辑帧率：每秒100帧
    private fixedDeltaTime: number = 0.01; // 固定时间间隔：10ms (100FPS)，50ms内产生5帧
    private logicFrameTimer: number = 0; // 逻辑帧计时器
    private currentLogicFrame: number = 0; // 当前逻辑帧

    // 简化的等待机制（保留基本的帧清理）
    private maxWaitFrames: number = 3; // 最大等待帧数



    // 动物动画管理
    private animalAnimations: Map<string, sp.Skeleton> = new Map(); // 每个玩家的骨骼动画组件
    private lastMovementTimes: Map<string, number> = new Map(); // 每个玩家最后移动时间
    private currentAnimationStates: Map<string, string> = new Map(); // 当前动画状态
    private readonly IDLE_TIMEOUT: number = 3000; // 3秒无移动触发待机动画

    // 提示动画管理
    private tishiAnimations: Map<string, sp.Skeleton> = new Map(); // 每个玩家的提示骨骼动画组件
    private tishiNodes: Map<string, cc.Node> = new Map(); // 每个玩家的提示节点

    // 动物类型映射
    private readonly ANIMAL_TYPES = {
        'red': 'huli',
        'yellow': 'daishu',
        'green': 'yingwu',
        'pink': 'zhu'
    };

    // 动画状态类型
    private readonly ANIMATION_STATES = {
        STATIC: 'jingzhi',  // 静止
        IDLE: 'daiji',      // 待机
        WALK: 'zoulu'       // 行走
    };

    // 渲染插值系统 - 让20FPS逻辑帧在60/120FPS渲染下变得丝滑
    private logicStates: Map<string, LogicState[]> = new Map(); // 每个玩家的逻辑状态历史
    private renderStates: Map<string, RenderState> = new Map(); // 每个玩家的当前渲染状态
    private maxLogicStateHistory: number = 8; // 减少历史状态数量，优化内存使用

    // 对象池优化 - 减少GC压力
    private vec2Pool: cc.Vec2[] = []; // Vec2对象池
    private poolSize: number = 50; // 对象池大小
    private poolIndex: number = 0; // 当前池索引

    // 平滑移动系统 - 解决直接设置位置导致的卡顿问题
    private targetPositions: Map<string, cc.Vec2> = new Map(); // 每个玩家的目标位置
    private playerMovingStates: Map<string, boolean> = new Map(); // 每个玩家是否正在移动到目标
    private moveSpeed: number = 400; // 像素/秒，控制追赶服务器位置的速度
    private positionThreshold: number = 4; // 距离阈值，小于此值认为已到达目标（速度400时放宽，避免抖动）
    // 速度400的情况下，限制和解阶段的最大追赶速度，避免瞬间吸附
    private maxReconciliationSpeed: number = 480; // 1.2x 本地速度
    // 回滚误差日志节流
    private lastErrorLogTime: number = 0;

    // 远端玩家显示策略：true 表示优先使用“渲染插值”，禁用基于 target 的追赶，以避免两套系统争抢写位置
    private useRenderInterpolationForRemotes: boolean = true;

    // 渲染延迟/抖动缓冲系统 - 优化版
    private renderDelay: number = 80; // 减少渲染延迟，提升响应性
    private jitterBufferSize: number = 150; // 减少缓冲区大小
    private adaptiveRenderDelay: number = 80; // 自适应渲染延迟
    private networkJitterHistory: number[] = []; // 网络抖动历史记录
    private maxJitterHistorySize: number = 10; // 减少历史记录数量
    private lastFrameReceiveTime: number = 0; // 上次接收帧的时间
    private frameReceiveIntervals: number[] = []; // 帧接收间隔历史

    // 抖动缓冲优化
    private jitterSum: number = 0; // 抖动总和（用于快速计算平均值）
    private jitterCount: number = 0; // 抖动计数

    // 网络指示状态缓存，避免频繁重复加载资源
    private _netStatus: 'good' | 'medium' | 'poor' = 'good';
    private readonly NET_ICON_PATH = {
        good: 'net_res/icon_signal_green',
        medium: 'net_res/icon_signal_yellow',
        poor: 'net_res/icon_signal_red'
    };
    private readonly NETWORK_BANNER_PATH = {
        medium: 'net_res/Network_bad',
        poor: 'net_res/Network_poor'
    };



    // 坐标显示测试功能
    private isCoordinateDisplayEnabled: boolean = true; // 是否启用坐标显示

    // FPS统计
    private fpsAccumTime: number = 0;
    private fpsFrameCount: number = 0;
    private currentFps: number = 0;

    // 方块动画管理器
    private blockAnimationManager: BlockAnimationManager | null = null;

    // 游戏事件处理器
    private gameEventHandler: SimpleGameEventHandler | null = null;

    // ========== 玩家掉落检测相关 ==========
    // 棋盘配置 - 大地图边界坐标
    private readonly BOARD_CONFIG = {
        // 大地图像素坐标范围（边界坐标）
        LEFT_X: -360,    // 左边界
        RIGHT_X: 360,    // 右边界
        BOTTOM_Y: -344,  // 下边界
        TOP_Y: 376,      // 上边界

        // 格子配置
        GRID_SIZE: 80,   // 与后端一致：总宽/9 = 718/9 ≈ 79.7777778
        GRID_COUNT: 9,   // 9x9格子

        // 玩家配置
        PLAYER_SIZE: 60, // 玩家（动物）的体积大小
        SURVIVAL_RATIO: 0.4 // 90%体积判定
    };

    // 掉落检测状态
    private fallenPlayers: Set<string> = new Set(); // 已掉落的玩家ID
    private fallingPlayers: Set<string> = new Set(); // 正在掉落中的玩家ID（位置锁定）
    private currentGamePhase: number = 1; // 当前游戏阶段
    private fallenBlocks: Set<string> = new Set(); // 已掉落的方块坐标集合 "x,y"
    private isInputBlocked: boolean = false; // 是否禁止输入
    private isFrameInputStopped: boolean = false; // 是否停止发送FrameInput（eventType: 7时触发）
    private isReconnecting: boolean = false; // 是否正在经历断线重连流程（收到权威帧后恢复发送）

    // ========== 倒计时系统状态 ==========
    private isCountdownActive: boolean = false; // 是否在倒计时阶段
    private lastCountdownEventId: string = ""; // 上次倒计时事件ID，防止重复处理
    private countdownTimer: number = 0; // 倒计时定时器ID
    private isWaitingPhase: boolean = false; // 是否在等待阶段（GameEventWaiting到GameEventRoundWaiting）
    // 硬冻结本地玩家（1~2期间绝对不动）
    private isLocalFrozen: boolean = false;
    private frozenLocalPos: cc.Vec2 = cc.Vec2.ZERO;

    // ========== 回合数显示状态    // 回合数显示相关
    private currentRound: number = 1; // 当前回合数

    // 死亡特效相关
    private deathEffectNodes: Map<string, cc.Node> = new Map(); // 玩家死亡特效节点映射

    /**
     * 检查本地玩家是否已死亡
     */
    private isLocalPlayerDead(): boolean {
        // 重连场景下 myPlayerData 可能尚未就绪（尤其是本地玩家已死亡不会生成动物节点），
        // 因此改为直接读取登录 userId 判断。
        const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
        if (!myUserId) return false;
        return this.fallenPlayers.has(myUserId);
    }

    /**
     * 根据 EnterRoom 的 users 预标记死亡玩家集合。
     * 确保在恢复各阶段(resume*Phase)之前，isLocalPlayerDead() 判断为真时不会短暂启用摇杆。
     */
    private premarkFallenFromEnterRoom(users: any[]): void {
        if (!Array.isArray(users)) return;
        // 以服务端为准重建死亡集合
        this.fallenPlayers.clear();
        for (const u of users) {
            if (u && u.bpStatus === 2 && u.userId) {
                this.fallenPlayers.add(u.userId);
            }
        }

        // 若本地玩家已经死亡，立即禁用输入与摇杆，避免任何启用闪烁
        const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
        if (myUserId && this.fallenPlayers.has(myUserId)) {
            this.isInputBlocked = true;
            this.isFrameInputStopped = true;
            this.currentPlayerVelocity = cc.Vec2.ZERO;
            if (this.joystickController) {
                this.joystickController.setInteractable(false);
            }
        }
    }

    /**
     * 获取总玩家数量（优先使用已缓存的 allPlayerData）
     */
    private getTotalPlayerCount(): number {
        if (this.allPlayerData && this.allPlayerData.size > 0) {
            return this.allPlayerData.size;
        }
        const users = GlobalBean.GetInstance().noticeStartGame?.users;
        return Array.isArray(users) ? users.length : 0;
    }

    /**
     * 获取存活玩家数量（= 总人数 - 已掉落人数）
     */
    private getAlivePlayerCount(): number {
        const total = this.getTotalPlayerCount();
        const fallen = this.fallenPlayers ? this.fallenPlayers.size : 0;
        return Math.max(0, total - fallen);
    }

    onLoad() {
        cc.director.getPhysicsManager().enabled = true;
        // 初始化对象池
        this.initializeObjectPool();
        // 初始化玩家头像状态节点
        this.initializeAllPlayerAvatarStatus();
    }

    protected onEnable(): void {
        // 页面激活时的初始化逻辑

    }

    /**
     * 初始化对象池 - 减少GC压力
     */
    private initializeObjectPool(): void {
        // 预创建Vec2对象池
        for (let i = 0; i < this.poolSize; i++) {
            this.vec2Pool.push(cc.v2(0, 0));
        }
    }

    /**
     * 从对象池获取Vec2对象
     */
    private getVec2FromPool(x: number = 0, y: number = 0): cc.Vec2 {
        if (this.vec2Pool.length > 0) {
            const vec = this.vec2Pool.pop();
            vec.x = x;
            vec.y = y;
            return vec;
        }
        return cc.v2(x, y);
    }

    /**
     * 将Vec2对象返回对象池
     */
    private returnVec2ToPool(vec: cc.Vec2): void {
        if (this.vec2Pool.length < this.poolSize) {
            vec.x = 0;
            vec.y = 0;
            this.vec2Pool.push(vec);
        }
    }

    /**
     * 将 GameFrame 中的方块事件(3/4/5)转换为 GameEventData 并转发到动画管理器与附加逻辑
     */
    private forwardBlockEventFromFrame(frameEvent: any): void {
        const anyEvent = frameEvent as any;
        const eventType: number = anyEvent.eventType;
        const evt = anyEvent.eventData || anyEvent;
        const blocksSrc = evt && (evt.blockChanges || evt.blocks);


        const gameEventData: GameEventData = {
            eventType,
            currentRound: (evt && evt.currentRound) || 1,
            countDown: (evt && evt.countDown) || 0,
            randomSeed: (evt && evt.randomSeed) || Math.floor(Math.random() * 100000),
            message: "",
            blocks: Array.isArray(blocksSrc) ? blocksSrc.map((b: any, idx: number) => ({
                id: (typeof b.id === 'number' ? b.id : (idx + 1)),
                x: b.x || 0,
                y: b.y || 0,
                blockType: 1,
                animationState: BlockAnimationState.NONE,
                animationDelay: 0
            })) : []
        };

        // 委托给方块动画管理器
        if (!this.blockAnimationManager) {
            this.initializeBlockAnimationManager();
        }
        if (this.blockAnimationManager) {
            this.blockAnimationManager.handleGameEvent(gameEventData);
        }

        // 执行附加逻辑（更新阶段、fallenBlocks 等）
        this.handleAdditionalEventLogic(gameEventData);
    }

    /**
     * 处理玩家死亡事件（eventType:7）
     * 要求：不影响方块动画(例如 eventType:4 正在进行)，仅针对对应玩家播放掉落动画。
     */
    private handlePlayerDeathEvent(gameEvent: any): void {
        if (!gameEvent) return;
        const anyEvent = gameEvent as any;
        const userId: string = anyEvent.userId || (anyEvent.eventData && anyEvent.eventData.userId);
        if (!userId) {
            console.warn("[GPC] 玩家死亡事件缺少 userId，无法播放掉落动画", gameEvent);
            return;
        }

        // 已经处理过掉落则不重复播放
        if (this.fallenPlayers.has(userId)) {
            return;
        }

        // 查找玩家动物节点
        const animalNode = this.allPlayerAnimals.get(userId);
        if (!animalNode) {
            console.warn(`[GPC] 未找到玩家 ${userId} 的动物节点，无法播放掉落动画`);
            return;
        }

        // 统一视角：尽量使用服务端权威坐标计算偏移方向，避免各端由于插值/同步差异导致方向不一致
        const evtData = anyEvent.eventData || anyEvent;
        const deathPixelX = typeof evtData?.pixelX === 'number' ? evtData.pixelX : undefined;
        const deathPixelY = typeof evtData?.pixelY === 'number' ? evtData.pixelY : undefined;

        // 新增：提取格子内掉落的致死格子坐标
        const deathGridX = typeof evtData?.gridX === 'number' ? evtData.gridX : undefined;
        const deathGridY = typeof evtData?.gridY === 'number' ? evtData.gridY : undefined;


        // 播放掉落动画（内部会计算合适的偏移并隐藏bg）
        this.executePlayerFall(userId, {
            forceOffset: true,
            pixelX: deathPixelX,
            pixelY: deathPixelY,
            gridX: deathGridX,
            gridY: deathGridY
        });
    }

    protected start(): void {
        // 初始化返回按钮
        Tools.imageButtonClick(this.boardBtnBack, Config.buttonRes + 'side_btn_back_normal', Config.buttonRes + 'side_btn_back_pressed', () => {
            this.isLeaveGameDialogShow = true
            this.leaveDialogController.show(1, () => {
                this.isLeaveGameDialogShow = false
            }, () => {
                // 退出游戏时清理视觉元素
                this.cleanupVisualElements();
                // 关键：确认退出本局后，立即停止一切帧输入的收集与发送
                this.isFrameInputStopped = true;
                this.isInputBlocked = true;
                this.frameInputBuffer = [];
                // 禁用摇杆交互，避免继续产生输入
                if (this.joystickController) {
                    this.joystickController.setInteractable(false);
                }
                // 停止本地移动速度，避免视觉残留
                this.isPlayerMoving = false;
                this.currentPlayerVelocity = cc.Vec2.ZERO;
            })
        });

        // 绑定 ForceCloseGame 按钮点击事件（若在编辑器中设置了该节点）
        if (this.forceCloseBtn) {
            this.forceCloseBtn.on(cc.Node.EventType.TOUCH_END, this.onClickForceCloseGame, this);
        }

        // 初始化摇杆控制
        this.initializeJoystick();

        // 初始化批量发送时间
        this.lastBatchSendTime = Date.now();

        // 初始化方块动画管理器
        this.initializeBlockAnimationManager();

        // 初始化游戏事件处理器
        this.initializeGameEventHandler();

        // 初始化坐标显示功能
        this.initializeCoordinateDisplay();

        // 初始化倒计时系统
        this.initializeCountdownSystem();

        // 初始化回合数显示系统
        this.initializeRoundDisplaySystem();

        // 初始化jingbao系统
        this.initializeJingbaoSystem();

        // 确保开局隐藏GameOver节点
        this.hideGameOver();

        // 设置infoClick回调，模仿HallPageController的实现方式
        this.infoClick = () => {
            if (this.infoDialogController) {
                this.infoDialogController.show(() => { });
            }
        };

        this.menuLayoutController.setClick(() => {
            //弹出游戏简介
            if (this.infoClick) {
                this.infoClick()
            }
        })
    }

    /**
     * 点击“强制关闭游戏”按钮，向后端发送 ForceCloseGame 消息
     */
    private onClickForceCloseGame(): void {
        try {
            WebSocketManager.GetInstance().sendMsg(MessageId.MsgTypeForceCloseGame, {});
            GameMgr.Console.Log('🔧 Sent ForceCloseGame');
        } catch (e) {
            cc.warn('Failed to send ForceCloseGame:', e);
        }
    }

    //设置按钮点击事件的回调的
    setClick(backClick: Function, infoClick: Function, settingClick: Function) {

        this.infoClick = infoClick
    }

    /**
     * 初始化所有玩家的动物预制体（统一管理）
     */
    private initializeAllPlayersAnimals(): void {
        const gameStartData = GlobalBean.GetInstance().noticeStartGame;
        if (!gameStartData || !gameStartData.users) {
            console.warn("游戏数据尚未初始化，无法生成动物预制体");
            return;
        }

        // 获取自己的用户ID
        const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
        if (!myUserId) {
            console.error("无法获取玩家ID");
            return;
        }

        // 清理旧的动物节点
        this.clearAllAnimals();

        // 延迟一帧再重新生成动物预制体，确保清理完全完成
        this.scheduleOnce(() => {
            this.createAllPlayerAnimals(gameStartData.users, myUserId);
        }, 0);
    }

    /**
     * 创建所有玩家的动物预制体
     */
    private createAllPlayerAnimals(playerDataArray: any[], myUserId: string): void {
        // 为每个玩家生成动物预制体
        for (const playerData of playerDataArray) {
            // 存储玩家数据
            this.allPlayerData.set(playerData.userId, playerData);

            // 生成动物预制体
            const animalNode = this.createAnimalPrefab(playerData.playerType, playerData.x, playerData.y, playerData.userId, playerData.playerNumber);
            if (animalNode) {
                // 存储动物节点
                this.allPlayerAnimals.set(playerData.userId, animalNode);

                // 如果是自己的动物，特别标记
                if (playerData.userId === myUserId) {
                    this.myPlayerData = playerData;
                    this.myAnimalNode = animalNode;
                    // 若正处于硬冻结（事件1已到，事件2未到），将冻结锚点更新为新生成节点的出生点
                    if (this.isLocalFrozen) {
                        this.frozenLocalPos = cc.v2(animalNode.x, animalNode.y);
                    }

                    // 开局时显示提示动画（只对本地玩家显示）
                    this.scheduleOnce(() => {
                        this.showTishiAnimation(playerData.userId);
                    }, 0.5); // 延迟0.5秒显示，确保动物动画初始化完成
                }
            }
        }


    }

    /**
     * 统一的GameStart入口：重置状态 -> 创建头像分数界面 -> 初始化动物预制体
     */
    public handleGameStart(): void {


        // 1. 完整重置游戏状态（确保所有方块显示、清理动画、重置同步状态等）
        this.resetGameState();

        // 2. 创建头像和分数界面
        if (this.gameLayoutController) {
            this.gameLayoutController.createScoreView();
        } else {
            console.error("gameLayoutController 未设置！请在编辑器中拖拽 GameLayoutController 组件");
        }

        // 3. 初始化所有玩家的动物预制体
        this.initializeAllPlayersAnimals();

        // 4. 设置方块皮肤（在方块动画管理器初始化之后）
        this.setBlockSkinFromMapType();


    }

    /**
     * 根据地图类型设置方块皮肤
     */
    private setBlockSkinFromMapType(): void {
        try {
            const gameStartData = GlobalBean.GetInstance().noticeStartGame;
            if (gameStartData && gameStartData.mapType) {
                const mapType = gameStartData.mapType;


                // 使用全局管理器的静态单例
                const globalMgr = GlobalManagerController.Instance;
                if (globalMgr && typeof globalMgr.setBlockSkin === 'function') {
                    globalMgr.setBlockSkin(mapType);
                } else {
                    console.warn('GlobalManagerController 单例未找到或 setBlockSkin 方法不存在');
                    // 备用方案：直接调用 BlockAnimationManager
                    this.setBlockSkinDirectly(mapType);
                }
            } else {
                console.warn('无法获取地图类型，跳过方块皮肤设置');
            }
        } catch (e) {
            console.warn('设置方块皮肤失败:', e);
            // 备用方案：直接调用 BlockAnimationManager
            try {
                const gameStartData = GlobalBean.GetInstance().noticeStartGame;
                if (gameStartData && gameStartData.mapType) {
                    this.setBlockSkinDirectly(gameStartData.mapType);
                }
            } catch (e2) {
                console.warn('备用方案也失败:', e2);
            }
        }
    }

    /**
     * 直接设置方块皮肤（备用方案）
     */
    private setBlockSkinDirectly(mapType: number): void {
        const suffix = mapType === 2 ? '2' : '1';
        const blockSkin = `zhuankuai${suffix}`;



        try {
            if (this.blockAnimationManager && typeof (this.blockAnimationManager as any).setBlockSkin === 'function') {
                (this.blockAnimationManager as any).setBlockSkin(blockSkin);

            } else {
                console.warn('BlockAnimationManager 未初始化或 setBlockSkin 方法不存在');
            }
        } catch (e) {
            console.warn('直接设置方块皮肤失败:', e);
        }
    }

    /**
     * 创建动物预制体
     * @param playerType 玩家类型
     * @param x 初始位置x坐标 
     * @param y 初始位置y坐标
     * @param userId 玩家ID
     * @param playerNumber 玩家位置编号
     * @returns 创建的动物节点
     */
    private createAnimalPrefab(playerType: string, x: number, y: number, userId: string, playerNumber: number): cc.Node {
        if (!this.animalPrefab) {
            console.error("动物预制体未设置");
            return null;
        }

        // 检查动物类型是否有效
        if (!this.ANIMAL_TYPES[playerType]) {
            console.error(`未知的playerType: ${playerType}`);
            return null;
        }

        // 实例化统一动物预制体
        const animalNode = cc.instantiate(this.animalPrefab);

        // 计算安全出生点：当坐标无效或落在默认中心(0,0)时，按座位号落到四角
        const isValidNum = (v: any) => typeof v === 'number' && !Number.isNaN(v) && Number.isFinite(v);
        let spawnX = isValidNum(x) ? x : Number.NaN;
        let spawnY = isValidNum(y) ? y : Number.NaN;

        const isCenter = (vx: number, vy: number) => isValidNum(vx) && isValidNum(vy) && Math.abs(vx) < 1e-3 && Math.abs(vy) < 1e-3;

        if (!isValidNum(spawnX) || !isValidNum(spawnY) || isCenter(spawnX, spawnY)) {
            // 使用服务端规范的默认出生点（单位：像素）
            switch (playerNumber) {
                case 1: // 左上
                    spawnX = -240;
                    spawnY = 258;
                    break;
                case 2: // 右上
                    spawnX = 240;
                    spawnY = 258;
                    break;
                case 3: // 左下
                    spawnX = -240;
                    spawnY = -225;
                    break;
                default: // 4或其他 -> 右下
                    spawnX = 240;
                    spawnY = -255;
                    break;
            }
            cc.warn(`[Spawn] 无效或缺失坐标，使用默认出生点 userId=${userId}, playerNumber=${playerNumber}, fallback=(${spawnX.toFixed(1)}, ${spawnY.toFixed(1)})`, { x, y });
        }

        // 设置初始位置
        animalNode.setPosition(spawnX, spawnY);

        // 设置初始朝向：1号位和2号位（上方位置）的动物头朝下
        if (playerNumber === 1 || playerNumber === 2) {
            animalNode.angle = 180;

        }

        // 添加到场景中
        if (this.playerNode) {
            this.playerNode.addChild(animalNode);
        } else {
            this.node.addChild(animalNode);
        }

        // 设置bg节点可见性（只有本地玩家显示bg节点）
        this.setupBgNodeVisibility(animalNode, userId);

        // 初始化动画系统
        this.initializeAnimalAnimation(animalNode, playerType, userId);

        return animalNode;
    }

    /**
     * 设置bg节点可见性（只有本地玩家显示bg节点）
     * @param animalNode 动物节点
     * @param userId 玩家ID
     */
    private setupBgNodeVisibility(animalNode: cc.Node, userId: string): void {
        const bgNode = animalNode.getChildByName('bg');
        if (bgNode) {
            // 获取本地玩家ID
            const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
            const isLocalPlayer = (myUserId === userId);
            bgNode.active = isLocalPlayer;

        } else {
            console.warn(`动物节点中未找到bg子节点`);
        }
    }

    // 已删除showStartCountdown方法 - 倒计时统一由GameEventWaiting消息触发

    /**
     * 根据 EnterRoom 数据初始化客户端帧ID，使断线重连后的 FrameInput 帧号从后端给定数字开始。
     * 兼容多种后端字段：
     * - nextClientFrameId / clientFrameStart / clientStartFrameId / startFrameId / nextFrameId
     * - clientFrameId（API更新后的新字段，表示断线前最新的客户端帧ID，则下一帧为 +1）
     * - currentClientFrameId（表示当前已用的最后一帧，则下一帧为 +1）
     * - lastClientFrameId（表示最后一帧号，则下一帧为 +1）
     */
    private initializeClientFrameFromReconnection(enterRoomData: any): void {
        let startId: number | null = null;
        const d: any = enterRoomData || {};

        // 直接表示“下一帧”的优先
        if (typeof d.nextClientFrameId === 'number') startId = d.nextClientFrameId;
        else if (typeof d.clientFrameStart === 'number') startId = d.clientFrameStart;
        else if (typeof d.clientStartFrameId === 'number') startId = d.clientStartFrameId;
        else if (typeof d.startFrameId === 'number') startId = d.startFrameId;
        else if (typeof d.nextFrameId === 'number') startId = d.nextFrameId;

        // 通过“当前/最后一帧”推导下一帧
        if (startId === null) {
            if (typeof d.clientFrameId === 'number') startId = d.clientFrameId + 1; // 新增：API更新后的字段
            else if (typeof d.currentClientFrameId === 'number') startId = d.currentClientFrameId + 1;
            else if (typeof d.lastClientFrameId === 'number') startId = d.lastClientFrameId + 1;
        }

        // 兜底：不做修改，仅记录日志
        if (startId === null) {
            console.warn('[Reconnection] 未从 EnterRoom 中解析到客户端起始帧号，保持现有 clientFrameId:', this.clientFrameId);
            return;
        }

        // 使下一次 ++clientFrameId 等于后端给定的 startId
        this.clientFrameId = startId - 1;
        // 清空并重置缓冲与计时（避免用旧帧号/旧时间片）
        this.frameInputBuffer = [];
        this.lastBatchSendTime = Date.now();


    }

    /**
     * 【修复1】掉落动画触发前隐藏bg节点，防止动画消失
     * @param animalNode 动物节点
     * @param userId 玩家ID
     */
    private hideBgNodeBeforeFall(animalNode: cc.Node, userId: string): void {
        const bgNode = animalNode.getChildByName('bg');
        if (bgNode) {
            // 获取本地玩家ID
            const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
            const isLocalPlayer = (myUserId === userId);

            // 只有本地玩家才需要隐藏bg节点（因为只有本地玩家的bg节点是可见的）
            if (isLocalPlayer) {
                // 掉落动画触发前隐藏bg节点，防止与掉落动画冲突
                bgNode.active = false;

            }
        }
    }

    /**
     * 初始化动物动画系统
     * @param animalNode 动物节点
     * @param playerType 玩家类型
     * @param userId 玩家ID
     */
    private initializeAnimalAnimation(animalNode: cc.Node, playerType: string, userId: string): void {
        // 获取dongwu子节点上的骨骼动画组件
        const dongwuNode = animalNode.getChildByName('dongwu');
        if (!dongwuNode) {
            console.error(`动物节点中未找到dongwu子节点`);
            return;
        }

        const skeletonComponent = dongwuNode.getComponent(sp.Skeleton);
        if (!skeletonComponent) {
            console.error(`dongwu节点缺少Skeleton组件`);
            return;
        }

        // 检查骨骼动画数据是否加载完成
        if (!skeletonComponent.skeletonData) {
            console.error(`Skeleton数据未加载`);
            return;
        }

        // 存储骨骼动画组件引用
        this.animalAnimations.set(userId, skeletonComponent);

        // 初始化提示动画系统
        this.initializeTishiAnimation(animalNode, userId);

        // 获取动物类型
        const animalType = this.ANIMAL_TYPES[playerType];
        if (!animalType) {
            console.error(`无效的玩家类型: ${playerType}`);
            return;
        }

        // 设置初始动画状态为静止
        const initialAnimationName = `${this.ANIMATION_STATES.STATIC}/${animalType}`;

        // 初始化计时器和状态
        this.lastMovementTimes.set(userId, Date.now());
        this.currentAnimationStates.set(userId, this.ANIMATION_STATES.STATIC);

        // 延迟播放动画，确保骨骼动画完全初始化
        this.tryPlayInitialAnimation(userId, initialAnimationName, 0);
    }

    /**
     * 尝试播放初始动画（带重试机制）
     * @param userId 玩家ID
     * @param animationName 动画名称
     * @param retryCount 重试次数
     */
    private tryPlayInitialAnimation(userId: string, animationName: string, retryCount: number): void {
        const maxRetries = 5;
        const delayTime = 0.1 + retryCount * 0.1; // 0.1s, 0.2s, 0.3s, 0.4s, 0.5s

        this.scheduleOnce(() => {
            const skeletonComponent = this.animalAnimations.get(userId);
            if (!skeletonComponent) {
                return;
            }

            // 检查动画是否存在
            if (skeletonComponent.skeletonData) {
                try {
                    const animationData = skeletonComponent.findAnimation(animationName);
                    if (animationData) {
                        // 动画存在，播放它
                        this.playAnimalAnimation(userId, animationName, true);
                        return;
                    }
                } catch (e) {
                    // 继续重试
                }
            }

            // 动画不存在，尝试重试
            if (retryCount < maxRetries - 1) {
                this.tryPlayInitialAnimation(userId, animationName, retryCount + 1);
            } else {
                // 尝试播放一个可能存在的默认动画
                this.tryPlayFallbackAnimation(userId);
            }
        }, delayTime);
    }

    /**
     * 尝试播放备用动画
     * @param userId 玩家ID
     */
    private tryPlayFallbackAnimation(userId: string): void {
        const skeletonComponent = this.animalAnimations.get(userId);
        if (!skeletonComponent || !skeletonComponent.skeletonData) {
            return;
        }

        // 尝试一些常见的动画名称格式
        const fallbackAnimations = [
            'jingzhi',      // 只有动作名
            'idle',         // 英文名
            'static',       // 英文名
            'default'       // 默认动画
        ];

        for (const animName of fallbackAnimations) {
            try {
                const animationData = skeletonComponent.findAnimation(animName);
                if (animationData) {
                    this.playAnimalAnimation(userId, animName, true);
                    return;
                }
            } catch (e) {
                // 继续尝试下一个
            }
        }
    }

    /**
     * 初始化提示动画系统
     * @param animalNode 动物节点
     * @param userId 玩家ID
     */
    private initializeTishiAnimation(animalNode: cc.Node, userId: string): void {
        // 查找tishi子节点
        const tishiNode = animalNode.getChildByName('tishi');
        if (!tishiNode) {

            return;
        }

        // 获取tishi节点上的骨骼动画组件
        const tishiSkeletonComponent = tishiNode.getComponent(sp.Skeleton);
        if (!tishiSkeletonComponent) {
            console.warn(`tishi节点缺少Skeleton组件`);
            return;
        }

        // 检查骨骼动画数据是否加载完成
        if (!tishiSkeletonComponent.skeletonData) {
            console.warn(`tishi节点Skeleton数据未加载`);
            return;
        }

        // 存储提示动画组件和节点引用
        this.tishiAnimations.set(userId, tishiSkeletonComponent);
        this.tishiNodes.set(userId, tishiNode);

        // 初始时隐藏提示节点
        tishiNode.active = false;

    }

    /**
     * 显示提示动画（只对本地玩家）
     * @param userId 玩家ID
     */
    private showTishiAnimation(userId: string): void {
        const tishiNode = this.tishiNodes.get(userId);
        const tishiSkeletonComponent = this.tishiAnimations.get(userId);

        if (!tishiNode || !tishiSkeletonComponent) {

            return;
        }

        try {
            // 显示提示节点
            tishiNode.active = true;

            // 播放提示动画
            tishiSkeletonComponent.setAnimation(0, "tishi", true); // 循环播放

        } catch (error) {
            console.error(`播放提示动画失败:`, error);
        }
    }

    /**
     * 隐藏提示动画
     * @param userId 玩家ID，如果不传则隐藏所有玩家的提示
     */
    private hideTishiAnimation(userId?: string): void {
        if (userId) {
            // 隐藏指定玩家的提示
            const tishiNode = this.tishiNodes.get(userId);
            if (tishiNode) {
                tishiNode.active = false;

            }
        } else {
            // 隐藏所有玩家的提示
            this.tishiNodes.forEach((tishiNode, playerId) => {
                if (tishiNode) {
                    tishiNode.active = false;
                }
            });

        }
    }

    /**
     * 播放动物动画
     * @param userId 玩家ID
     * @param animationName 动画名称
     * @param loop 是否循环播放
     */
    private playAnimalAnimation(userId: string, animationName: string, loop: boolean = false): void {
        const skeletonComponent = this.animalAnimations.get(userId);
        if (!skeletonComponent) {
            console.warn(`玩家 ${userId} 的骨骼动画组件不存在`);
            return;
        }

        try {
            // 播放骨骼动画
            skeletonComponent.setAnimation(0, animationName, loop);
        } catch (error) {
            console.error(`播放骨骼动画失败 ${animationName}:`, error);
        }
    }

    /**
     * 更新玩家动画状态
     * @param userId 玩家ID
     * @param isMoving 是否正在移动
     */
    private updatePlayerAnimationState(userId: string, isMoving: boolean): void {
        const playerData = this.allPlayerData.get(userId);
        if (!playerData) return;

        const animalType = this.ANIMAL_TYPES[playerData.playerType];
        if (!animalType) return;

        const currentState = this.currentAnimationStates.get(userId);
        const currentTime = Date.now();

        if (isMoving) {
            // 正在移动，播放行走动画
            this.lastMovementTimes.set(userId, currentTime);

            if (currentState !== this.ANIMATION_STATES.WALK) {
                const walkAnimationName = `${this.ANIMATION_STATES.WALK}/${animalType}`;
                this.playAnimalAnimation(userId, walkAnimationName, true);
                this.currentAnimationStates.set(userId, this.ANIMATION_STATES.WALK);
            }
        } else {
            // 停止移动
            const lastMovementTime = this.lastMovementTimes.get(userId) || currentTime;
            const timeSinceLastMovement = currentTime - lastMovementTime;

            if (currentState === this.ANIMATION_STATES.WALK) {
                // 从行走状态切换到静止状态
                const staticAnimationName = `${this.ANIMATION_STATES.STATIC}/${animalType}`;
                this.playAnimalAnimation(userId, staticAnimationName, true);
                this.currentAnimationStates.set(userId, this.ANIMATION_STATES.STATIC);
            } else if (currentState === this.ANIMATION_STATES.STATIC && timeSinceLastMovement >= this.IDLE_TIMEOUT) {
                // 静止超过3秒，播放待机动画
                const idleAnimationName = `${this.ANIMATION_STATES.IDLE}/${animalType}`;
                this.playAnimalAnimation(userId, idleAnimationName, false);
                this.currentAnimationStates.set(userId, this.ANIMATION_STATES.IDLE);

                // 待机动画播放完成后回到静止状态
                this.scheduleIdleAnimationComplete(userId, animalType);
            }
        }
    }

    /**
     * 安排待机动画完成后的处理
     * @param userId 玩家ID
     * @param animalType 动物类型
     */
    private scheduleIdleAnimationComplete(userId: string, animalType: string): void {
        const skeletonComponent = this.animalAnimations.get(userId);
        if (!skeletonComponent) return;

        // 监听骨骼动画完成事件
        skeletonComponent.setCompleteListener(() => {
            // 待机动画完成，回到静止状态
            const staticAnimationName = `${this.ANIMATION_STATES.STATIC}/${animalType}`;
            this.playAnimalAnimation(userId, staticAnimationName, true);
            this.currentAnimationStates.set(userId, this.ANIMATION_STATES.STATIC);
            // 清理监听器
            skeletonComponent.setCompleteListener(null);
        });
    }

    /**
     * 清理所有动物节点
     */
    private clearAllAnimals(): void {
        // 销毁所有动物节点
        this.allPlayerAnimals.forEach((animalNode) => {
            if (animalNode && cc.isValid(animalNode)) {
                animalNode.destroy();
            }
        });

        // 清理数据
        this.allPlayerAnimals.clear();
        this.allPlayerData.clear();
        this.myAnimalNode = null;
        this.myPlayerData = null;

        // 清理动画相关数据
        this.animalAnimations.clear();
        this.lastMovementTimes.clear();
        this.currentAnimationStates.clear();

        // 清理提示动画相关数据
        this.tishiAnimations.clear();
        this.tishiNodes.clear();

        // 清理待机动画检查定时器
        this.clearIdleAnimationTimer();
    }

    /**
     * 获取指定玩家的动物节点
     * @param userId 玩家ID
     * @returns 动物节点
     */
    public getPlayerAnimal(userId: string): cc.Node {
        return this.allPlayerAnimals.get(userId);
    }

    /**
     * 获取所有玩家的动物节点
     * @returns 所有玩家动物节点的Map
     */
    public getAllPlayerAnimals(): Map<string, cc.Node> {
        return this.allPlayerAnimals;
    }

    /**
     * 初始化摇杆控制
     */
    private initializeJoystick(): void {
        if (!this.joystickController) {
            console.warn("摇杆控制器未设置，跳过摇杆初始化");
            return;
        }

        // 启用点击生成模式
        this.joystickController.enableClickToGenerate = true;

        // 设置摇杆开始回调
        this.joystickController.setOnJoystickStart((_inputData: JoystickInputData) => {
            // 检查本地玩家是否死亡，死亡后不允许移动
            if (this.isLocalPlayerDead()) {
                this.isPlayerMoving = false;
                this.currentPlayerVelocity = cc.Vec2.ZERO;
                return;
            }
            // 仅依赖摇杆可交互状态来决定是否允许开始移动
            if (!this.joystickController.isInteractable()) {
                this.isPlayerMoving = false;
                this.currentPlayerVelocity = cc.Vec2.ZERO;
                return;
            }
            this.isPlayerMoving = true;
        });

        // 设置摇杆移动回调
        this.joystickController.setOnJoystickMove((inputData: JoystickInputData) => {
            // 检查本地玩家是否死亡，死亡后不允许移动
            if (this.isLocalPlayerDead()) {
                this.currentPlayerVelocity = cc.Vec2.ZERO;
                return;
            }
            // 仅依赖摇杆可交互状态
            if (!this.joystickController.isInteractable()) {
                this.currentPlayerVelocity = cc.Vec2.ZERO;
                return;
            }
            // 计算玩家移动速度
            const effectiveSpeed = this.playerMoveSpeed * this.moveSpeedScale;
            // 注意：VirtualJoystickController 中的 inputData.x / y 已经包含了力度（force），
            // 即 x = direction.x * force，y = direction.y * force，范围均为 [-1,1]
            // 因此前端预测应使用 x/y 直接乘以速度，不能再额外乘 force，否则会导致速度被平方(f^2)
            // 与后端的 UpdatePlayerPosition 保持一致：new = current + input * effectiveSpeed * dt
            this.currentPlayerVelocity.x = inputData.x * effectiveSpeed;
            this.currentPlayerVelocity.y = inputData.y * effectiveSpeed;
        });

        // 设置摇杆结束回调
        this.joystickController.setOnJoystickEnd(() => {
            this.isPlayerMoving = false;
            this.currentPlayerVelocity = cc.Vec2.ZERO;
        });
    }


    //结算
    setCongratsDialog(noticeSettlement: NoticeSettlement) {
        // 收到结算后立即停止一切输入发送，避免房间已销毁仍发送FrameInput
        this.isFrameInputStopped = true;
        this.isInputBlocked = true;
        this.frameInputBuffer = [];
        if (this.joystickController) {
            this.joystickController.setInteractable(false);
        }

        // 结算时清理视觉元素
        this.cleanupVisualElements();

        this.setCongrats(noticeSettlement)

        //退出弹窗正在显示的话  就先关闭
        if (this.isLeaveGameDialogShow) {
            this.leaveDialogController.hide()
        }

        this.isCongratsDialog = true
        //弹出结算弹窗
        this.congratsDialogController.show(noticeSettlement, () => {
            this.isCongratsDialog = false
        })

    }

    protected onDisable(): void {
        //退出弹窗正在显示的话  就先关闭
        if (this.isLeaveGameDialogShow) {
            this.leaveDialogController.hide()
        }

        //结算弹窗正在显示的话就先关闭掉
        if (this.isCongratsDialog) {
            this.congratsDialogController.hide()
        }
    }


    //结算
    setCongrats(noticeSettlement: NoticeSettlement) {

        const index = noticeSettlement.users.findIndex((item) => item.userId === GlobalBean.GetInstance().loginData.userInfo.userId);//搜索 
        if (index >= 0) { //自己参与的话 才会显示正常的胜利和失败的音效，自己不参与的话 就全部显示胜利的音效
            if (noticeSettlement.users[index].rank === 1) { //判断自己是不是第一名
                AudioManager.winAudio();
                this.particle.active = true;
                this.particle1.active = true;
                if (this.particle) {
                    this.particle.getComponent(cc.ParticleSystem).resetSystem();
                }
                if (this.particle1) {
                    this.particle1.getComponent(cc.ParticleSystem).resetSystem();

                }
            } else {
                AudioManager.loseAudio();
                this.particle.active = false;
                this.particle1.active = false;
            }
        } else {
            AudioManager.winAudio();
            this.particle.active = true;
            this.particle1.active = true;
            if (this.particle) {
                this.particle.getComponent(cc.ParticleSystem).resetSystem();
            }
            if (this.particle1) {
                this.particle1.getComponent(cc.ParticleSystem).resetSystem();

            }
        }

    }

    /**
     * 每帧更新 - 分离逻辑帧和渲染帧，实现丝滑渲染插值
     */
    protected update(dt: number): void {
        // 收集输入数据（移除dt依赖）
        this.collectFrameInput();

        // 检查并发送批量输入
        this.checkAndSendBatchInput();

        // 更新逻辑帧计时器
        this.logicFrameTimer += dt;

        // 执行逻辑帧（固定10ms间隔，100FPS - 确保50ms内产生5帧）
        while (this.logicFrameTimer >= this.fixedDeltaTime) {
            this.executeLogicFrame();
            this.logicFrameTimer -= this.fixedDeltaTime;
            this.currentLogicFrame++;
        }

        // 【关键】本地玩家即时移动更新 - 客户端预测
        this.updateLocalPlayerMovement(dt);

        // 【关键】平滑移动更新 - 让其他玩家向目标位置平滑移动
        this.updateSmoothMovement(dt);

        // 【关键】渲染插值更新 - 让后端权威位置在60/120FPS下丝滑显示
        this.updateRenderInterpolation(dt);


        // 硬冻结：在等待期间（1~2之间）强制把本地玩家拉回冻结点，彻底杜绝一切位移来源
        if (this.isLocalFrozen && this.myAnimalNode && cc.isValid(this.myAnimalNode)) {
            this.myAnimalNode.x = this.frozenLocalPos.x;
            this.myAnimalNode.y = this.frozenLocalPos.y;
        }

        // 更新调试信息（FPS/网络/和解等）
        this.updateDebugInfo(dt);
    }

    // 缓存精度计算常量以提升性能
    private readonly PRECISION_FACTOR = Math.pow(10, this.PRECISION_DECIMALS);

    // 碰撞检测策略配置
    private collisionStrategy: 'aggressive' | 'conservative' | 'hybrid' = 'hybrid';
    private lastServerPosition: cc.Vec2 | null = null;
    private consecutiveCollisionFrames: number = 0;
    // 碰撞跟随后端模式：被其他玩家顶住时，前端不再做本地预测，严格跟随后端位置
    private isCollisionFollowing: boolean = false;
    private collisionReleaseFrames: number = 0; // 连续无碰撞帧计数，用于退出碰撞跟随模式

    // 和解频率监控 - 优化版
    private reconciliationHistory: number[] = [];
    private readonly MAX_RECONCILIATION_HISTORY = 10;
    private readonly FREQUENT_RECONCILIATION_THRESHOLD = 6; // 降低阈值：10秒内超过6次和解认为频繁
    private consecutiveOverThresholdFrames: number = 0; // 连续超过和解阈值的帧计数（仅本地玩家使用）

    // 预测缓冲优化
    private localPredictionBuffer: cc.Vec2 = cc.v2(0, 0); // 本地预测缓冲位置
    private predictionConfidence: number = 1.0; // 预测置信度
    private networkDelayCompensation: number = 0; // 网络延迟补偿

    // 和解优化参数 - 更保守的设置
    private reconciliationSmoothness: number = 0.04; // 更平滑的和解系数（速度400下减小瞬时拉扯）
    private adaptiveThreshold: number = 20; // 调整为20，降低触发和解的距离阈值
    private lastReconciliationTime: number = 0; // 上次和解时间
    private maxReconciliationThreshold: number = 90; // 最大阈值限制（允许根据网络状况上调）

    // 弱网优化系统
    private networkQuality: 'good' | 'poor' | 'bad' = 'good'; // 网络质量评估
    private packetLossRate: number = 0; // 丢包率
    private averageLatency: number = 0; // 平均延迟
    private latencyHistory: number[] = []; // 延迟历史
    private lastPacketTime: number = 0; // 上次收包时间
    private missedPackets: number = 0; // 丢失包数
    private totalPackets: number = 0; // 总包数
    private packetWindow: number[] = []; // 固定窗口记录最近N个包是否丢失（1丢 0未丢）
    private packetWindowSize: number = 100; // 丢包统计窗口大小

    // 弱网下的特殊策略
    private weakNetworkMode: boolean = false; // 是否启用弱网模式
    private extrapolationEnabled: boolean = false; // 是否启用外推预测
    private positionBufferSize: number = 5; // 位置缓冲区大小
    private playerPositionBuffers: Map<string, cc.Vec2[]> = new Map(); // 玩家位置缓冲

    /**
     * 【核心】本地玩家即时移动更新 - 智能碰撞处理策略
     */
    private updateLocalPlayerMovement(dt: number): void {
        // 渲染帧不再推进本地位置，改为仅更新动画和朝向，避免与固定步长重复推进
        if (!this.myAnimalNode || !cc.isValid(this.myAnimalNode) || !this.myPlayerData) {
            return;
        }
        // 仅在可交互且未被阻止输入时，更新朝向/动画（不移动位置）
        const canInteract = !!this.joystickController && this.joystickController.isInteractable() && !this.isInputBlocked && !this.isLocalPlayerDead();
        if (!canInteract) {
            return;
        }

        if (this.currentPlayerVelocity.mag() > 0.01) {
            // 更新朝向与动画表现（不做位置加法）
            this.updateAnimalRotation(this.myAnimalNode, this.currentPlayerVelocity, dt);
            this.updatePlayerAnimationState(this.myPlayerData.userId, true);
        } else {
            this.consecutiveCollisionFrames = 0;
            this.updatePlayerAnimationState(this.myPlayerData.userId, false);
        }
    }

    /**
     * 优化的和解机制 - 减少回滚卡顿
     */
    private applyOptimizedReconciliation(dt: number, noInput: boolean = false): void {
        if (!this.myAnimalNode || !this.lastServerPosition) return;

        const currentTime = Date.now();
        const localPos = this.myAnimalNode.getPosition();

        // 使用对象池获取向量，避免频繁创建
        const localVec = this.getVec2FromPool(localPos.x, localPos.y);
        const delta = this.getVec2FromPool(
            this.lastServerPosition.x - localVec.x,
            this.lastServerPosition.y - localVec.y
        );

        const distance = Math.sqrt(delta.x * delta.x + delta.y * delta.y);

        // 碰撞跟随：直接对齐到权威位置，避免误差累积
        if (this.isCollisionFollowing) {
            this.myAnimalNode.x = this.lastServerPosition.x;
            this.myAnimalNode.y = this.lastServerPosition.y;
            // 返回向量到对象池
            this.returnVec2ToPool(localVec);
            this.returnVec2ToPool(delta);
            return;
        }

        // 安全保护：误差极大时直接对齐，防止发散
        if (distance > 200) {
            this.myAnimalNode.x = this.lastServerPosition.x;
            this.myAnimalNode.y = this.lastServerPosition.y;
            this.returnVec2ToPool(localVec);
            this.returnVec2ToPool(delta);
            return;
        }

        // 动态调整校正强度
        let correctionStrength = noInput ? this.reconciliationSmoothness * 0.7 : this.reconciliationSmoothness;

        // 根据距离调整校正强度 - 距离越远，校正越温和
        if (distance > 15) {
            correctionStrength *= 0.6; // 远距离时更温和
        } else if (distance > 8) {
            correctionStrength *= 0.8; // 中距离时稍微温和
        }

        // 根据和解频率动态调整 - 更激进的降低（碰撞跟随下跳过此弱化）
        const timeSinceLastReconciliation = currentTime - this.lastReconciliationTime;
        if (!this.isCollisionFollowing) {
            if (timeSinceLastReconciliation < 1000) { // 1秒内有和解
                correctionStrength *= 0.3; // 大幅降低校正强度
            } else if (timeSinceLastReconciliation < 2000) { // 2秒内有和解
                correctionStrength *= 0.6; // 适度降低校正强度
            }
        }

        // 只有在误差足够大时才进行校正
        if (distance > 1.0) {
            const correction = this.getVec2FromPool(
                delta.x * correctionStrength,
                delta.y * correctionStrength
            );

            this.myAnimalNode.x = localPos.x + correction.x;
            this.myAnimalNode.y = localPos.y + correction.y;
            // 打印一次性纠正日志（节流）
            const now = Date.now();
            if (now - this.lastErrorLogTime > 200) {

                this.lastErrorLogTime = now;
            }

            // 返回向量到对象池
            this.returnVec2ToPool(correction);
        }

        // 返回向量到对象池
        this.returnVec2ToPool(localVec);
        this.returnVec2ToPool(delta);
    }

    /**
     * 激进策略：积极预测移动，边缘滑动
     */
    private handleAggressiveMovement(currentPos: cc.Vec2, predictedX: number, predictedY: number, displacement: cc.Vec2): void {
        // 碰撞时：取消一切本地碰撞解算（不滑、不缩步），进入和解等待后端权威位置
        if (this.checkLocalCollision(predictedX, predictedY)) {
            this.consecutiveCollisionFrames++;
            if (this.myPlayerData) {
                this.reconciliationStates.set(this.myPlayerData.userId, true);
            }
            // 不改变当前位置，但保持行走动画反馈输入
            this.updatePlayerAnimationState(this.myPlayerData.userId, true);
            return;
        }

        // 无碰撞：正常应用预测位移
        this.consecutiveCollisionFrames = 0;
        this.myAnimalNode.x = predictedX;
        this.myAnimalNode.y = predictedY;
        this.updateAnimalRotation(this.myAnimalNode, this.currentPlayerVelocity, GamePageController.LOGIC_FRAME_DT);
        this.updatePlayerAnimationState(this.myPlayerData.userId, true);
    }

    /**
     * 保守策略：当检测到碰撞时，限制移动幅度
     */
    private handleConservativeMovement(currentPos: cc.Vec2, predictedX: number, predictedY: number, displacement: cc.Vec2): void {
        // 发生碰撞即取消本地预测，不再尝试缩步/安全位移，等待后端权威位置
        if (this.checkLocalCollision(predictedX, predictedY)) {
            this.consecutiveCollisionFrames++;
            if (this.myPlayerData) {
                this.reconciliationStates.set(this.myPlayerData.userId, true);
            }
            // 不移动，但保持行走动画反馈
            this.updatePlayerAnimationState(this.myPlayerData.userId, true);
            return;
        }

        // 无碰撞：按预测位移前进（仍然保持“保守策略”的旋转/动画一致性）
        this.consecutiveCollisionFrames = 0;
        this.myAnimalNode.x = predictedX;
        this.myAnimalNode.y = predictedY;
        this.updateAnimalRotation(this.myAnimalNode, this.currentPlayerVelocity, GamePageController.LOGIC_FRAME_DT);
        this.updatePlayerAnimationState(this.myPlayerData.userId, true);
    }

    /**
     * 混合策略：根据连续碰撞情况动态调整
     */
    private handleHybridMovement(currentPos: cc.Vec2, predictedX: number, predictedY: number, displacement: cc.Vec2): void {
        // 如果连续碰撞次数较多，采用保守策略
        if (this.consecutiveCollisionFrames > 10) {
            this.handleConservativeMovement(currentPos, predictedX, predictedY, displacement);
            return;
        }

        // 否则使用激进策略
        this.handleAggressiveMovement(currentPos, predictedX, predictedY, displacement);
    }

    /**
     * 前端碰撞检测 - 完全匹配后端AABB碰撞检测算法
     * 玩家体积为60*60像素，坐标为中心点
     */
    private checkLocalCollision(predictedX: number, predictedY: number): boolean {
        const playerSize = 70.0;           // 玩家体积60*60，与后端保持一致
        const halfSize = playerSize / 2.0; // 30.0

        // 计算当前玩家新位置的碰撞箱（AABB）
        const currentLeft = predictedX - halfSize;
        const currentRight = predictedX + halfSize;
        const currentTop = predictedY + halfSize;
        const currentBottom = predictedY - halfSize;

        // 检查与所有其他存活玩家的碰撞
        let hasCollision = false;
        this.allPlayerAnimals.forEach((otherAnimalNode, userId) => {
            // 跳过自己
            if (this.myPlayerData && userId === this.myPlayerData.userId) {
                return;
            }

            // 跳过已掉落的玩家（对应后端的BPStatus检查）
            if (this.fallenPlayers.has(userId)) {
                return;
            }

            // 获取其他玩家的当前位置
            const otherPos = otherAnimalNode.getPosition();

            // 计算其他玩家的碰撞箱
            const otherLeft = otherPos.x - halfSize;
            const otherRight = otherPos.x + halfSize;
            const otherTop = otherPos.y + halfSize;
            const otherBottom = otherPos.y - halfSize;

            // AABB碰撞检测算法（与后端完全一致）
            if (currentLeft < otherRight && currentRight > otherLeft &&
                currentTop > otherBottom && currentBottom < otherTop) {
                // 发生碰撞

                hasCollision = true;
            }
        });

        return hasCollision;
    }

    /**
     * 寻找有效的移动位置 - 沿着碰撞边缘滑动
     */
    private findValidMovePosition(currentX: number, currentY: number, displacement: cc.Vec2): cc.Vec2 | null {
        // 尝试只在X轴移动
        const xOnlyX = currentX + displacement.x;
        const xOnlyY = currentY;
        if (!this.checkLocalCollision(xOnlyX, xOnlyY)) {
            return cc.v2(xOnlyX, xOnlyY);
        }

        // 尝试只在Y轴移动
        const yOnlyX = currentX;
        const yOnlyY = currentY + displacement.y;
        if (!this.checkLocalCollision(yOnlyX, yOnlyY)) {
            return cc.v2(yOnlyX, yOnlyY);
        }

        // 尝试减少移动距离（50%的位移）
        const halfX = currentX + displacement.x * 0.5;
        const halfY = currentY + displacement.y * 0.5;
        if (!this.checkLocalCollision(halfX, halfY)) {
            return cc.v2(halfX, halfY);
        }

        // 无法移动
        return null;
    }

    /**
     * 记录碰撞上下文信息，用于调试前后端差异
     */
    private logCollisionContext(frontendX: number, frontendY: number, serverX: number, serverY: number): void {


        // 检查前端是否检测到碰撞
        const frontendCollision = this.checkLocalCollision(frontendX, frontendY);
        const serverCollision = this.checkLocalCollision(serverX, serverY);



        // 记录其他玩家位置

        this.allPlayerAnimals.forEach((animalNode, userId) => {
            if (this.myPlayerData && userId === this.myPlayerData.userId) return;
            if (this.fallenPlayers.has(userId)) return;

            const pos = animalNode.getPosition();
            const distance = Math.sqrt(Math.pow(frontendX - pos.x, 2) + Math.pow(frontendY - pos.y, 2));

        });
    }

    /**
     * 记录和解事件，并根据频率调整策略
     */
    private recordReconciliation(): void {
        const currentTime = Date.now();
        // 碰撞跟随后端期间的和解不计入频率统计，避免策略被动降级
        if (this.isCollisionFollowing) {
            return;
        }
        this.reconciliationHistory.push(currentTime);

        // 保持历史记录在限制范围内
        if (this.reconciliationHistory.length > this.MAX_RECONCILIATION_HISTORY) {
            this.reconciliationHistory.shift();
        }

        // 检查是否频繁和解（最近10秒内）
        const recentReconciliations = this.reconciliationHistory.filter(time =>
            currentTime - time < 10000 // 10秒内
        );

        if (recentReconciliations.length >= this.FREQUENT_RECONCILIATION_THRESHOLD) {

            this.collisionStrategy = 'conservative';

            // 5秒后重置为混合策略
            setTimeout(() => {
                this.collisionStrategy = 'hybrid';

            }, 5000);
        }
    }

    /**
     * 公开方法：手动切换碰撞策略（调试用）
     */
    public setCollisionStrategy(strategy: 'aggressive' | 'conservative' | 'hybrid'): void {
        this.collisionStrategy = strategy;

    }

    /**
     * 公开方法：获取当前碰撞策略状态
     */
    public getCollisionStatus(): object {
        return {
            strategy: this.collisionStrategy,
            consecutiveCollisions: this.consecutiveCollisionFrames,
            recentReconciliations: this.reconciliationHistory.length,
            reconciliationHistory: this.reconciliationHistory.slice(-5) // 最近5次
        };
    }

    // ========== 倒计时系统实现 ==========

    /**
     * 隐藏回合数显示
     */
    private hideRoundDisplay(): void {
        if (this.roundDisplayNode) {
            this.roundDisplayNode.active = false;
        }
    }

    /**
     * 获取多语言文本（简化版本）
     * @param key 多语言key
     * @param params 参数数组
     * @returns 本地化文本
     */
    private getI18nText(key: string, params: string[] = []): string {
        // 这里应该集成真正的多语言系统，现在使用简化版本
        const defaultTexts = {
            'round_display': '第{0}回合' // 默认中文
        };

        let text = defaultTexts[key] || key;

        // 替换参数
        params.forEach((param, index) => {
            text = text.replace(`{${index}}`, param);
        });

        return text;
    }

    /**
     * 初始化倒计时系统
     */
    private initializeCountdownSystem(): void {
        if (!this.countdownNode) {
            console.warn("倒计时节点未设置，跳过倒计时系统初始化");
            return;
        }

        // 初始状态下隐藏倒计时节点
        this.countdownNode.active = false;

        // 重置倒计时状态
        this.isCountdownActive = false;
        this.lastCountdownEventId = "";


    }

    /**
     * 初始化回合数显示系统
     */
    private initializeRoundDisplaySystem(): void {
        if (!this.roundDisplayNode) {
            console.warn("回合数显示节点未设置，跳过回合数显示系统初始化");
            return;
        }

        // 开局时隐藏节点
        this.roundDisplayNode.active = false;

        // 重置回合数
        this.currentRound = 1;


    }

    /**
     * 更新回合数显示
     * @param roundNumber 回合数
     */
    private updateRoundDisplay(roundNumber: number): void {
        if (!this.roundDisplayNode) {
            console.warn("回合数显示节点未设置");
            return;
        }

        // 更新当前回合数
        this.currentRound = roundNumber;

        // 显示节点（第一次显示时才激活）
        if (!this.roundDisplayNode.active) {
            this.roundDisplayNode.active = true;
        }

        // 查找子节点上的Label组件
        const labelComponent = this.roundDisplayNode.getComponentInChildren(cc.Label);
        if (!labelComponent) {
            console.warn("回合数显示节点的子节点中未找到Label组件");
            return;
        }

        // 优先使用插件的 LocalizedLabel
        try {
            // 优先在与 Label 同一节点上获取 LocalizedLabel
            const labelNode = labelComponent.node;
            let localized: any = labelNode.getComponent('LocalizedLabel');
            if (!localized) {
                // 若未挂载则在运行时添加（编辑器里可直接拖挂）
                localized = labelNode.addComponent('LocalizedLabel');
            }
            if (localized) {
                // 设置多语言 key 与参数
                localized.key = 'round_display';
                if (typeof localized.bindParam === 'function') {
                    localized.bindParam(roundNumber.toString());
                } else if (typeof localized.bindParamStrArray === 'function') {
                    localized.bindParamStrArray([roundNumber.toString()]);
                }
                // LocalizedLabel.refresh 会在 setter 或 bindParam 内部调用，不需要手动设置 string
                return;
            }
        } catch (e) {
            // 如果插件组件不可用，回退到内置简化版
            console.warn('LocalizedLabel 不可用，回退到内置多语言实现', e);
        }

        // 回退方案：使用简化版多语言函数
        const i18nText = this.getI18nText('round_display', [roundNumber.toString()]);
        labelComponent.string = i18nText;


    }

    /**
     * 处理倒计时开始（GameEventWaiting）
     */
    private handleCountdownStart(_eventData: GameEventData): void {
        // 等待阶段：禁用摇杆并显示倒计时动画

        if (this.joystickController) {
            this.joystickController.setInteractable(false);
        }
        // 清空待发送的输入帧，防止残留
        this.frameInputBuffer = [];
        // 重置本地移动状态
        this.isPlayerMoving = false;
        this.currentPlayerVelocity = cc.Vec2.ZERO;
        // 彻底取消本地玩家的任何平滑和解/目标位移，避免等待期间被推进
        if (this.myPlayerData) {
            const myId = this.myPlayerData.userId;
            this.reconciliationStates.delete(myId);
            this.playerMovingStates.set(myId, false);
            this.targetPositions.delete(myId);
            // 启动硬冻结（安全获取本地玩家当前位置）
            this.isLocalFrozen = true;
            if (this.myAnimalNode && cc.isValid(this.myAnimalNode)) {
                try {
                    const pos: any = (this.myAnimalNode as any).getPosition ? (this.myAnimalNode as any).getPosition() : { x: (this.myAnimalNode as any).x, y: (this.myAnimalNode as any).y };
                    const px = (pos && typeof pos.x === 'number') ? pos.x : 0;
                    const py = (pos && typeof pos.y === 'number') ? pos.y : 0;
                    this.frozenLocalPos = cc.v2(px, py);
                } catch (e) {
                    this.frozenLocalPos = cc.v2(0, 0);
                }
            } else {
                this.frozenLocalPos = cc.v2(0, 0);
            }
        }
        // 显示倒计时节点并播放动画（播放完成后自动隐藏）
        this.playCountdownAnimation();
    }

    /**
     * 处理倒计时结束（GameEventRoundWaiting）
     */
    private handleCountdownEnd(eventData?: GameEventData): void {


        // 新逻辑：结束等待阶段，仅恢复摇杆交互；
        // 不再由此处控制倒计时节点的显示/隐藏，统一交给骨骼动画完成事件处理。
        // 但如果本地玩家已死亡，则保持摇杆禁用状态
        if (this.joystickController && !this.isLocalPlayerDead()) {
            this.joystickController.setInteractable(true);
            // 摇杆启用时隐藏提示动画
            this.hideTishiAnimation();
        }

        // 结束等待阶段
        this.isWaitingPhase = false;

        // 解除本地玩家的硬冻结状态
        this.isLocalFrozen = false;

        // 更新回合数显示（在回合等待状态时显示当前回合数）
        if (eventData && eventData.currentRound) {
            this.updateRoundDisplay(eventData.currentRound);
        }

        // 隐藏jingbao节点（在回合等待阶段隐藏难度升级动画）
        this.hideJingbaoAnimation();


    }

    /**
     * 播放倒计时动画
     */
    private playCountdownAnimation(): void {
        if (!this.countdownNode) {
            console.error("❌ 倒计时节点未设置");
            return;
        }

        // 防止重复调用：如果已经在播放中，则忽略
        if (this.isCountdownActive) {
            console.warn("⚠️ 倒计时动画已在播放中，忽略重复调用");
            return;
        }



        // 确保节点可见在最前层
        this.countdownNode.active = true;
        this.countdownNode.zIndex = 9999;
        this.countdownNode.opacity = 255;


        // 从倒计时节点获取骨骼动画组件
        const skeletonComponent = this.countdownNode.getComponent(sp.Skeleton);
        if (!skeletonComponent) {
            console.error("❌ 倒计时节点上未找到骨骼动画组件");
            return;
        }

        // 立即设置为激活状态，防止重复调用
        this.isCountdownActive = true;

        // 检查父节点状态
        let parentNode = this.countdownNode.parent;
        while (parentNode) {
            if (!parentNode.active) {
                console.warn(`⚠️ 倒计时节点的父节点 ${parentNode.name} 不可见，可能影响显示`);
            }
            parentNode = parentNode.parent;
        }

        // 首先尝试直接播放，如果失败再使用重试机制
        if (skeletonComponent.skeletonData) {

            this.playCountdownAnimationDirect(skeletonComponent);
        } else {

            this.tryPlayCountdownAnimation(skeletonComponent, 0);
        }
    }

    /**
     * 直接播放倒计时动画（骨骼数据已准备好的情况）
     */
    private playCountdownAnimationDirect(skeletonComponent: sp.Skeleton): void {
        try {
            // 播放动画
            skeletonComponent.setCompleteListener(null); // 清除旧的监听，避免重复回调

            // 检查动画是否存在
            try {
                const animationData = skeletonComponent.skeletonData;
                if (animationData && (animationData as any).animations) {
                    const animations = (animationData as any).animations;
                    const goAnimation = animations.find((anim: any) => anim.name === "go");
                    if (goAnimation) {

                    } else {
                        console.warn(`⚠️ 未找到"go"动画，可能导致立即完成`);

                    }
                } else {
                    console.warn(`⚠️ 无法获取动画数据`);
                }
            } catch (error) {
                console.warn(`⚠️ 检查动画时出错:`, error);
            }

            skeletonComponent.setAnimation(0, "go", false);

            // 播放321go音效
            this.play321GoSoundEffect();

            // 检查动画时长，如果过短则使用备用方案
            let animationDuration = 0;
            try {
                const animationData = skeletonComponent.skeletonData;
                if (animationData && (animationData as any).animations) {
                    const animations = (animationData as any).animations;
                    const goAnimation = animations.find((anim: any) => anim.name === "go");
                    if (goAnimation && goAnimation.duration) {
                        animationDuration = goAnimation.duration;
                    }
                }
            } catch (error) {
                console.warn("获取动画时长失败:", error);
            }



            // 验证节点状态

            // 动画播放结束后自动隐藏并重置标志
            if (animationDuration > 0 && animationDuration < 1.0) {
                // 如果动画时长小于1秒，使用人工延迟确保用户能看到

                this.scheduleOnce(() => {

                    if (this.countdownNode) {
                        this.countdownNode.active = false;

                    }
                    this.isCountdownActive = false;
                    // 清除监听，避免下次重复
                    if (skeletonComponent) {
                        skeletonComponent.setCompleteListener(null);
                    }
                }, 2.0); // 固定显示2秒
            } else {
                // 动画时长正常，使用动画完成回调
                skeletonComponent.setCompleteListener(() => {

                    if (this.countdownNode) {
                        this.countdownNode.active = false;

                    }
                    this.isCountdownActive = false;
                    // 清除监听，避免下次重复
                    if (skeletonComponent) {
                        skeletonComponent.setCompleteListener(null);
                    }
                });
            }



            // 多次验证节点状态
            this.scheduleOnce(() => {

            }, 0.05); // 50ms后验证

            this.scheduleOnce(() => {

            }, 0.1); // 100ms后验证

            this.scheduleOnce(() => {
                if (this.countdownNode && this.countdownNode.active) {

                } else {
                    console.error(`❌ 延迟验证: 倒计时节点不可见！可能被其他代码隐藏了`);
                    console.error(`❌ 验证时状态: isCountdownActive=${this.isCountdownActive}, countdownNode存在=${!!this.countdownNode}`);
                }
            }, 0.2); // 200ms后验证

        } catch (error) {
            console.error("❌ 直接播放倒计时动画失败:", error);
            this.tryPlayCountdownAnimation(skeletonComponent, 0);
        }
    }

    /**
     * 递增延迟重试播放倒计时动画
     */
    private tryPlayCountdownAnimation(skeletonComponent: sp.Skeleton, retryCount: number): void {
        const maxRetries = 5;
        const delayTime = 0.1 + retryCount * 0.1; // 递增延迟：0.1s, 0.2s, 0.3s, 0.4s, 0.5s

        this.scheduleOnce(() => {
            try {
                // 检查动画是否存在
                if (!skeletonComponent || !skeletonComponent.skeletonData) {
                    console.warn(`❌ 倒计时动画重试${retryCount + 1}/${maxRetries}：骨骼数据未准备好`);
                    if (retryCount < maxRetries - 1) {
                        this.tryPlayCountdownAnimation(skeletonComponent, retryCount + 1);
                    } else {
                        console.error("❌ 倒计时动画重试失败，尝试备用方案");
                        this.tryPlayFallbackCountdownAnimation(skeletonComponent);
                    }
                    return;
                }

                // 骨骼数据准备好后，调用直接播放方法
                this.playCountdownAnimationDirect(skeletonComponent);

            } catch (error) {
                console.error(`❌ 倒计时动画重试${retryCount + 1}/${maxRetries}失败:`, error);
                if (retryCount < maxRetries - 1) {
                    this.tryPlayCountdownAnimation(skeletonComponent, retryCount + 1);
                } else {
                    console.error("❌ 倒计时动画重试失败，尝试备用方案");
                    this.tryPlayFallbackCountdownAnimation(skeletonComponent);
                }
            }
        }, delayTime);
    }

    /**
     * 备用倒计时动画播放方案
     */
    private tryPlayFallbackCountdownAnimation(skeletonComponent: sp.Skeleton): void {
        try {
            // 尝试播放常见的备用动画名称
            const fallbackAnimations = ["go", "default", "idle", "321go"];

            for (const animName of fallbackAnimations) {
                try {
                    // 直接尝试播放动画，不进行复杂的检查
                    skeletonComponent.setCompleteListener(null);
                    skeletonComponent.setAnimation(0, animName, false);
                    this.isCountdownActive = true;

                    // 播放321go音效
                    this.play321GoSoundEffect();

                    // 设置完成监听
                    skeletonComponent.setCompleteListener(() => {
                        if (this.countdownNode) {
                            this.countdownNode.active = false;
                        }
                        this.isCountdownActive = false;
                        if (skeletonComponent) {
                            skeletonComponent.setCompleteListener(null);
                        }
                    });

                    return;
                } catch (e) {
                    console.warn(`备用动画${animName}播放失败:`, e);
                }
            }

            // 如果所有备用动画都失败，至少保证音效播放和节点隐藏
            console.error("❌ 所有倒计时动画都失败，只播放音效");
            this.play321GoSoundEffect();

            // 延迟隐藏节点（模拟动画时长）
            this.scheduleOnce(() => {
                if (this.countdownNode) {
                    this.countdownNode.active = false;
                }
                this.isCountdownActive = false;
            }, 2.0); // 假设动画时长约2秒

        } catch (error) {
            console.error("❌ 备用倒计时动画方案也失败:", error);
            // 最后的保底方案：直接隐藏节点
            if (this.countdownNode) {
                this.countdownNode.active = false;
            }
            this.isCountdownActive = false;
        }
    }

    /**
     * 【核心】平滑移动更新 - 让其他玩家向目标位置平滑移动
     */
    private updateSmoothMovement(dt: number): void {
        this.allPlayerAnimals.forEach((animalNode, userId) => {
            // 跳过已掉落或正在掉落的玩家（避免干扰掉落动画）
            if (this.fallenPlayers.has(userId) || this.fallingPlayers.has(userId)) {
                return;
            }

            // 检查是否是本地玩家
            const isLocalPlayer = (this.myPlayerData && userId === this.myPlayerData.userId);
            const isReconciling = this.reconciliationStates.get(userId) || false;

            // 远端玩家若启用“渲染插值”，则不走目标追赶平滑，避免双写导致的抖动
            if (!isLocalPlayer && this.useRenderInterpolationForRemotes) {
                return;
            }

            // 等待阶段时，彻底禁止本地玩家的任何位移（包括和解导致的平滑移动）
            if (isLocalPlayer && this.isWaitingPhase) {
                this.playerMovingStates.set(userId, false);
                this.reconciliationStates.delete(userId);
                return;
            }

            // 本地玩家只在和解状态下才使用平滑移动
            if (isLocalPlayer && !isReconciling) {
                return;
            }

            const targetPosition = this.targetPositions.get(userId);
            const isMoving = this.playerMovingStates.get(userId);

            if (!targetPosition || !isMoving) {
                return;
            }

            const currentPosition = animalNode.getPosition();
            const currentVec2 = cc.v2(currentPosition.x, currentPosition.y);

            // 计算方向和距离（使用clone避免修改原向量）
            const direction = targetPosition.clone().sub(currentVec2);
            const distance = direction.mag();

            // 如果已经很接近目标位置，直接到达并停止移动
            if (distance < this.positionThreshold) {
                animalNode.x = targetPosition.x;
                animalNode.y = targetPosition.y;
                this.playerMovingStates.set(userId, false);
                this.reconciliationStates.delete(userId);
                return;
            }

            // 计算移动速度上限（和解状态下提高，但限制最大追赶速度）
            let currentMoveSpeed = isReconciling ? this.moveSpeed * 1.5 : this.moveSpeed;
            if (isReconciling) {
                currentMoveSpeed = Math.min(currentMoveSpeed, this.maxReconciliationSpeed);
            }

            // 指数平滑比例（FPS无关）：alpha = 1 - exp(-k*dt)
            const k = 10.0; // 收敛速率，手感参数
            const alpha = 1 - Math.exp(-k * Math.max(0, dt));

            // 期望按比例靠拢，但每帧位移不超过速度上限
            const desiredStep = distance * alpha;
            const maxStep = currentMoveSpeed * dt;
            const step = Math.min(desiredStep, maxStep);

            // 防止超调：如果这一步会超过剩余距离，直接到达目标
            if (step >= distance) {
                animalNode.x = targetPosition.x;
                animalNode.y = targetPosition.y;
                this.playerMovingStates.set(userId, false);
                this.reconciliationStates.delete(userId);
                if (isLocalPlayer) {

                }
            } else {
                // 平滑移动一小段距离（指数平滑 + 速度上限）
                const normalizedDirection = direction.normalize();
                const displacement = normalizedDirection.mul(step);
                animalNode.x = currentPosition.x + displacement.x;
                animalNode.y = currentPosition.y + displacement.y;

                // 更新玩家动画状态
                this.updatePlayerAnimationState(userId, true);
                if (isLocalPlayer) {
                    const remain = distance - step;
                    // 可根据需要使用 remain 做额外处理
                }
            }
        });
    }

    /**
     * 精度控制工具函数 - 四舍五入保留6位小数（性能优化版）
     */
    private roundToPrecision(value: number): number {
        return Math.round(value * this.PRECISION_FACTOR) / this.PRECISION_FACTOR;
    }


    /**
     * 执行逻辑帧 - 简化版：不再进行前端位置计算
     */
    private executeLogicFrame(): void {
        // 固定步长推进本地玩家预测与和解，确保与服务器10ms节奏一致
        if (!this.myAnimalNode || !cc.isValid(this.myAnimalNode) || !this.myPlayerData) {
            return;
        }

        // 🔒 如果本地玩家正在掉落，完全禁止逻辑帧更新
        if (this.fallingPlayers.has(this.myPlayerData.userId)) {
            return;
        }

        // 若不可交互/被阻止/已死亡，则不推进本地预测
        const canInteract = !!this.joystickController && this.joystickController.isInteractable() && !this.isInputBlocked && !this.isLocalPlayerDead();
        const isReconciling = this.reconciliationStates.get(this.myPlayerData.userId) || false;

        // 额外：即使无输入，也检测当前与其他玩家是否发生重叠碰撞
        // 若发生碰撞，则强制进入“跟随后端”模式与和解状态
        const currentPosForCollision = this.myAnimalNode.getPosition();
        const collidingNow = this.checkLocalCollision(currentPosForCollision.x, currentPosForCollision.y);
        if (collidingNow) {
            this.isCollisionFollowing = true;
            this.collisionReleaseFrames = 0;
            // 标记和解，等待后端权威位置推进
            this.reconciliationStates.set(this.myPlayerData.userId, true);
        } else {
            // 连续若干帧无碰撞则释放跟随模式
            if (this.isCollisionFollowing) {
                this.collisionReleaseFrames++;
                if (this.collisionReleaseFrames >= 3) {
                    this.isCollisionFollowing = false;
                    this.collisionReleaseFrames = 0;
                }
            }
        }

        // 碰撞跟随模式下，无论是否有输入，都跳过本地预测，直接按后端进行和解
        if (!this.isCollisionFollowing && canInteract && this.currentPlayerVelocity.mag() > 0.01) {
            const currentPos = this.myAnimalNode.getPosition();
            const compensatedVelocity = this.applyPredictionCompensation();
            const displacement = compensatedVelocity.mul(GamePageController.LOGIC_FRAME_DT);
            const predictedX = currentPos.x + displacement.x;
            const predictedY = currentPos.y + displacement.y;

            switch (this.collisionStrategy) {
                case 'aggressive':
                    this.handleAggressiveMovement(cc.v2(currentPos.x, currentPos.y), predictedX, predictedY, displacement);
                    break;
                case 'conservative':
                    this.handleConservativeMovement(cc.v2(currentPos.x, currentPos.y), predictedX, predictedY, displacement);
                    break;
                case 'hybrid':
                default:
                    this.handleHybridMovement(cc.v2(currentPos.x, currentPos.y), predictedX, predictedY, displacement);
                    break;
            }

            // 预测后在固定步中进行和解校正，减小误差
            if (isReconciling && this.lastServerPosition) {
                if (!cc.isValid(this.myAnimalNode)) return;
                this.applyOptimizedReconciliation(GamePageController.LOGIC_FRAME_DT);
            }
        } else {
            // 无输入或处于碰撞跟随模式：持续进行和解（如果需要）
            if ((isReconciling || this.isCollisionFollowing) && this.lastServerPosition) {
                this.applyOptimizedReconciliation(GamePageController.LOGIC_FRAME_DT, true);
            }
        }
    }








    /**
     * 定期检查所有玩家的待机动画状态
     */
    private checkIdleAnimations(): void {
        const currentTime = Date.now();

        this.allPlayerData.forEach((playerData, userId) => {
            const lastMovementTime = this.lastMovementTimes.get(userId);
            const currentState = this.currentAnimationStates.get(userId);

            if (!lastMovementTime || !currentState) return;

            const timeSinceLastMovement = currentTime - lastMovementTime;

            // 只有在静止状态下才检查待机动画
            if (currentState === this.ANIMATION_STATES.STATIC &&
                timeSinceLastMovement >= this.IDLE_TIMEOUT) {

                const animalType = this.ANIMAL_TYPES[playerData.playerType];
                if (animalType) {
                    const idleAnimationName = `${this.ANIMATION_STATES.IDLE}/${animalType}`;
                    this.playAnimalAnimation(userId, idleAnimationName, false);
                    this.currentAnimationStates.set(userId, this.ANIMATION_STATES.IDLE);

                    // 待机动画播放完成后回到静止状态
                    this.scheduleIdleAnimationComplete(userId, animalType);
                }
            }
        });
    }

    /**
     * 初始化待机动画检查定时器
     */
    private initIdleAnimationTimer(): void {
        // 性能优化：延长待机动画检查间隔，减少CPU占用
        this.schedule(this.checkIdleAnimations, 2.0); // 从1秒改为2秒
    }

    /**
     * 清理待机动画检查定时器
     */
    private clearIdleAnimationTimer(): void {
        this.unschedule(this.checkIdleAnimations);
    }

    /**
     * 更新调试信息到 debugInfoLabel（中文参数）
     */
    private updateDebugInfo(dt: number): void {
        if (!this.debugInfoLabel) return;

        // FPS 统计
        this.fpsAccumTime += dt;
        this.fpsFrameCount += 1;
        let needRefresh = false;
        if (this.fpsAccumTime >= 0.5) { // 每0.5秒刷新一次，避免频繁更新
            this.currentFps = Math.round(this.fpsFrameCount / this.fpsAccumTime);
            this.fpsAccumTime = 0;
            this.fpsFrameCount = 0;
            needRefresh = true;
        }
        // 若没有新的FPS也每1秒兜底刷新一次
        if (!needRefresh) return;

        // 网络抖动均值
        const avgJitter = this.jitterCount > 0 ? (this.jitterSum / this.jitterCount) : 0;
        // 帧接收间隔均值（ms）
        let avgFrameInterval = 0;
        if (this.frameReceiveIntervals && this.frameReceiveIntervals.length > 0) {
            const sum = this.frameReceiveIntervals.reduce((a, b) => a + b, 0);
            avgFrameInterval = Math.round(sum / this.frameReceiveIntervals.length);
        }
        // 最近误差（本地与服务器位置差，仅本地玩家）
        let diffStr = "-";
        let diffVal = 0;
        if (this.myAnimalNode && this.lastServerPosition && cc.isValid(this.myAnimalNode)) {
            const p = this.myAnimalNode.getPosition();
            const dx = this.lastServerPosition.x - p.x;
            const dy = this.lastServerPosition.y - p.y;
            const d = Math.sqrt(dx * dx + dy * dy);
            diffVal = d;
            diffStr = d.toFixed(1);
        }
        // 最近10秒和解次数
        const now = Date.now();
        const recentRecon = this.reconciliationHistory.filter(t => now - t < 10000).length;

        // 更新网络状态图标（net/Network）
        this.updateNetworkIndicators(diffVal, now);

        // 碰撞与跟随状态
        let collisionStr = "-";
        if (this.myAnimalNode && cc.isValid(this.myAnimalNode)) {
            const p = this.myAnimalNode.getPosition();
            const collidingNow = this.checkLocalCollision(p.x, p.y);
            collisionStr = collidingNow ? '是' : '否';
        }
        const followStr = this.isCollisionFollowing ? '是' : '否';

        // 组装中文调试信息
        const lines: string[] = [];
        lines.push(`FPS: ${this.currentFps}`);
        lines.push(`网络质量: ${this.networkQuality}  平均延迟(ms): ${Math.round(this.averageLatency)}  抖动(ms): ${Math.round(avgJitter)}  丢包率: ${(this.packetLossRate * 100).toFixed(1)}%`);
        lines.push(`帧接收间隔均值(ms): ${avgFrameInterval}  渲染延迟(ms): ${Math.round(this.adaptiveRenderDelay)}`);
        lines.push(`和解阈值: ${Math.round(this.adaptiveThreshold)}  最大阈值: ${Math.round(this.maxReconciliationThreshold)}  预测置信度: ${this.predictionConfidence.toFixed(2)}`);
        lines.push(`本地速度(px/s): ${this.moveSpeed}  和解追赶上限(px/s): ${this.maxReconciliationSpeed}`);
        lines.push(`弱网模式: ${this.weakNetworkMode ? '是' : '否'}  外推: ${this.extrapolationEnabled ? '开' : '关'}`);
        lines.push(`本地-服务器位置差(px): ${diffStr}`);
        lines.push(`最近10秒和解次数: ${recentRecon}`);
        lines.push(`碰撞中: ${collisionStr}  碰撞跟随: ${followStr}`);

        this.debugInfoLabel.string = lines.join('\n');
    }

    /**
     * 根据误差与和解频率更新网络状态显示，并结合networkQuality进行综合评估
     * good: 不回滚且网络质量良好
     * medium: 轻微且非连续回滚（误差<50 且近期有少量回滚，非连续）或网络质量一般
     * poor: 连续回滚（误差≥50 且连续超阈，或短时内和解频繁）或网络质量差
     * 
     * 综合评估规则：
     * - networkQuality为'bad'时，good->poor, medium->poor
     * - networkQuality为'poor'时，good->medium
     */
    private updateNetworkIndicators(diffVal: number, nowTs: number): void {
        // 统计短窗内的和解次数，判断是否连续
        const last3sRecon = this.reconciliationHistory.filter(t => nowTs - t < 3000).length;
        const last10sRecon = this.reconciliationHistory.filter(t => nowTs - t < 10000).length;

        let nextStatus: 'good' | 'medium' | 'poor' = 'good';

        // 连续回滚：误差≥50 且连续超过和解阈值帧数>=3，或3秒内和解>=3次
        if ((diffVal >= 50 && this.consecutiveOverThresholdFrames >= 3) || last3sRecon >= 3) {
            nextStatus = 'poor';
        } else if (last10sRecon > 0 && this.consecutiveOverThresholdFrames < 3 && diffVal < 50) {
            // 轻微、非连续：10秒内有回滚，但不连续，误差<50
            nextStatus = 'medium';
        } else {
            // 无回滚：10秒内没有和解记录
            nextStatus = last10sRecon === 0 ? 'good' : 'medium';
        }

        // 🎯 结合networkQuality进行综合评估
        // 如果networkQuality显示网络质量差，即使和解状态良好也要降级显示
        const originalStatus = nextStatus;
        if (this.networkQuality === 'bad' && nextStatus === 'good') {
            nextStatus = 'poor';
        } else if (this.networkQuality === 'poor' && nextStatus === 'good') {
            nextStatus = 'medium';
        } else if (this.networkQuality === 'bad' && nextStatus === 'medium') {
            nextStatus = 'poor';
        }


        if (nextStatus === this._netStatus) {
            return; // 状态未变化不重复加载
        }
        this._netStatus = nextStatus;

        // 更新 net 图标
        if (this.net) {
            const iconPath = this.NET_ICON_PATH[nextStatus];
            this.setNodeSprite(this.net, iconPath);
            this.net.active = true;
        }

        // 更新 Network 横幅（仅在 medium/poor 时显示）
        if (this.Network) {
            if (nextStatus === 'medium') {
                this.setNodeSprite(this.Network, this.NETWORK_BANNER_PATH.medium);
                this.Network.active = true;
            } else if (nextStatus === 'poor') {
                this.setNodeSprite(this.Network, this.NETWORK_BANNER_PATH.poor);
                this.Network.active = true;
            } else {
                this.Network.active = false;
            }
        }
    }

    // 安全设置节点图片（resources/ 下相对路径，不含前缀）
    private setNodeSprite(node: cc.Node, resPath: string): void {
        if (!node || !resPath) return;
        cc.resources.load(resPath, cc.SpriteFrame, (err: Error, spriteFrame: cc.SpriteFrame) => {
            if (err) {
                cc.warn(`[NetIcon] 资源加载失败: ${resPath}`, err);
                return;
            }
            if (!cc.isValid(node)) return;
            const sp = node.getComponent(cc.Sprite) || node.addComponent(cc.Sprite);
            sp.spriteFrame = spriteFrame;
        });
    }



    /**
     * 收集帧输入数据到缓冲区 - 严格收集5帧数据
     */
    private collectFrameInput(): void {
        // 若被要求暂停发送与采集，则直接返回，避免在重连初始化前产生无效帧
        if (this.isFrameInputStopped) return;
        if (!this.joystickController) return;
        // 如果本地玩家已死亡，不收集输入
        if (this.isLocalPlayerDead()) return;

        let inputX = 0;
        let inputY = 0;

        // 仅当摇杆可交互时才采集输入，否则发送零输入
        if (this.joystickController.isInteractable()) {
            const inputData = this.joystickController.getInputData();
            if (inputData) {
                inputX = this.roundToPrecision(inputData.x);
                inputY = this.roundToPrecision(inputData.y);
            }
        }

        // 构建单帧输入数据
        const frameInput: GameInputFrame = {
            frameId: ++this.clientFrameId,
            x: inputX,
            y: inputY,
            dt: 0.01 // 固定dt为0.01（10ms）
        };

        // 累积帧数据
        this.frameInputBuffer.push(frameInput);

        // 当累积到5帧时，标记为可以发送
        if (this.frameInputBuffer.length >= 5) {
            // 保持缓冲区只有5帧
            if (this.frameInputBuffer.length > 5) {
                this.frameInputBuffer = this.frameInputBuffer.slice(-5);
            }
        }
    }

    /**
     * 检查并发送批量输入数据（50ms间隔，严格5帧）
     */
    private checkAndSendBatchInput(): void {
        // 如果已停止发送FrameInput或摇杆不可交互（等待阶段），直接返回
        if (this.isFrameInputStopped || !this.joystickController || !this.joystickController.isInteractable()) {
            return;
        }
        // 如果本地玩家已死亡，不发送输入
        if (this.isLocalPlayerDead()) {
            return;
        }

        const currentTime = Date.now();

        // 检查是否到了发送时间且有足够的帧数据
        if (currentTime - this.lastBatchSendTime >= this.batchSendInterval) {
            // 严格要求必须有5帧数据才发送
            if (this.frameInputBuffer.length >= 5) {
                this.sendBatchInput();
                this.lastBatchSendTime = currentTime;
            } else {
                // 如果帧数不足5帧，需要补齐
                this.fillFramesToFive();
                if (this.frameInputBuffer.length === 5) {
                    this.sendBatchInput();
                    this.lastBatchSendTime = currentTime;
                }
            }
        }
    }
    /**
     * 补齐帧数到5帧
     */
    private fillFramesToFive(): void {
        const targetFrameCount = 5;
        const currentFrameCount = this.frameInputBuffer.length;

        if (currentFrameCount >= targetFrameCount) {
            return; // 已经有足够的帧
        }

        // 获取最后一帧作为补齐的模板（如果没有帧则使用零输入）
        const lastFrame = currentFrameCount > 0 ?
            this.frameInputBuffer[currentFrameCount - 1] : {
                frameId: this.clientFrameId,
                x: 0,
                y: 0,
                dt: 0.01
            };

        // 补齐到5帧
        for (let i = currentFrameCount; i < targetFrameCount; i++) {
            const fillFrame: GameInputFrame = {
                frameId: ++this.clientFrameId,
                x: lastFrame.x, // 使用最后一帧的输入值
                y: lastFrame.y,
                dt: 0.01 // 固定dt为0.01（10ms）
            };
            this.frameInputBuffer.push(fillFrame);
        }

    }

    private sendBatchInput(): void {
        // 严格检查必须是5帧
        if (this.frameInputBuffer.length !== 5) {
            console.error(`发送帧数错误: 期望5帧，实际${this.frameInputBuffer.length}帧`);
            return;
        }

        // 转换GameInputFrame为ClientInputFrame格式
        const clientInputs: ClientInputFrame[] = this.frameInputBuffer.map(frame => ({
            frameId: frame.frameId,
            x: frame.x,
            y: frame.y
        }));

        const batchData: FrameInputData = {
            clientInputs: clientInputs
        };

        // 发送到服务器
        WebSocketManager.GetInstance().sendMsg(MessageId.MsgTypeFrameInput, batchData);

        // 清空缓冲区
        this.frameInputBuffer = [];
    }
    private shouldCollectInput(inputData: GameInputFrame): boolean {
        // 简化逻辑：总是收集输入数据，让服务器处理去重
        return true;
    }

    /**
     * 处理游戏帧数据（从服务器接收）- 使用新的playerPositions更新位置
     */
    public handleGameFrame(gameFrameData: GameFrameData): void {

        // 记录网络抖动数据
        this.recordNetworkJitter();

        // 更新确认的帧ID
        this.confirmedFrameId = gameFrameData.serverFrameId;

        // 断线重连期间不在这里恢复发送输入：
        // 要求调整为“仅在收到 EnterRoom 并完成重建后才恢复发送”。
        // 因此这里不再解除 isFrameInputStopped，由 handleReconnection() 完成恢复。

        // 检查游戏事件，特别是eventType: 7（玩家死亡事件）
        if (gameFrameData.gameEvents) {
            const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
            for (const gameEvent of gameFrameData.gameEvents) {
                // 统一提取事件体，便于兼容 {eventType, eventData} 与扁平结构
                const anyEvent = gameEvent as any;
                const evtType: number = anyEvent.eventType;
                const evtData = anyEvent.eventData || anyEvent;

                if (gameEvent.eventType === 7) {
                    // 同时播放对应玩家的死亡（掉落）动画（不影响方块(eventType:4)的动画播放）
                    const deathUserId = anyEvent.userId || (anyEvent.eventData && anyEvent.eventData.userId);

                    this.handlePlayerDeathEvent(gameEvent);

                    // 只有当死亡的是本地玩家时，才停止发送FrameInput
                    if (deathUserId && myUserId && deathUserId === myUserId) {
                        this.isFrameInputStopped = true;
                    }
                }

                // 处理玩家状态事件（死亡、断线、退出、重连）
                if (gameEvent.eventType >= 7 && gameEvent.eventType <= 10) {
                    // 从eventData中提取userId（兼容不同的数据结构）
                    const userId = gameEvent.userId || (anyEvent.eventData && anyEvent.eventData.userId);

                    // 创建标准化的事件数据
                    const standardizedEvent: GameEventData = {
                        eventType: gameEvent.eventType,
                        userId: userId,
                        currentRound: anyEvent.eventData?.currentRound || 0,
                        countDown: anyEvent.eventData?.countDown || 0,
                        randomSeed: anyEvent.eventData?.randomSeed || 0,
                        message: anyEvent.eventData?.message || `玩家状态事件${gameEvent.eventType}`,
                        pixelX: anyEvent.eventData?.pixelX,
                        pixelY: anyEvent.eventData?.pixelY,
                        deathReason: anyEvent.eventData?.deathReason,
                        surviveTime: anyEvent.eventData?.surviveTime,
                        isAlive: anyEvent.eventData?.isAlive  // 🎯 提取isAlive字段（eventType:10使用）
                    };

                    this.handleGameEvent(standardizedEvent);
                }

                // 直接在这里转发方块相关事件(3-5)给方块动画管理器，确保动画必定触发
                if (gameEvent.eventType === 3 || gameEvent.eventType === 4 || gameEvent.eventType === 5) {
                    this.forwardBlockEventFromFrame(gameEvent);
                    // 播放对应的音效
                    this.playBlockSoundEffect(gameEvent.eventType);
                }

                // 新增：处理等待开始/结束事件(1/2)以驱动倒计时与摇杆锁定
                if (evtType === 1 || evtType === 2) {
                    const convertedEvent: GameEventData = {
                        eventType: evtType,
                        currentRound: (evtData && evtData.currentRound) || 1,
                        countDown: (evtData && evtData.countDown) || 0,
                        randomSeed: (evtData && evtData.randomSeed) || 0,
                        message: "",
                        blocks: []
                    };
                    // 使用统一的附加逻辑处理，内部会调用 handleCountdownStart/End
                    this.handleAdditionalEventLogic(convertedEvent);
                }

                // 新增：处理GameEventShowcase事件(11)
                if (gameEvent.eventType === 11) {

                    const convertedEvent: GameEventData = {
                        eventType: 11,
                        currentRound: (evtData && evtData.currentRound) || 1,
                        countDown: (evtData && evtData.countDown) || 0,
                        randomSeed: (evtData && evtData.randomSeed) || 0,
                        message: "",
                        blocks: []
                    };
                    // 调用handleAdditionalEventLogic处理
                    this.handleAdditionalEventLogic(convertedEvent);
                }

                // 收到结束事件(6)：立即停止输入与摇杆，避免继续发送FrameInput
                if (gameEvent.eventType === 6) {
                    this.isFrameInputStopped = true;
                    this.isInputBlocked = true;
                    this.frameInputBuffer = [];
                    if (this.joystickController) {
                        this.joystickController.setInteractable(false);
                    }
                }
            }
        }

        // 使用后端返回的playerPositions更新玩家位置
        if (gameFrameData.playerPositions) {
            this.applyPlayerPositions(gameFrameData.playerPositions);
        } else {
            // 如果没有playerPositions，检查是否有旧格式的playerInputs
            const anyData = gameFrameData as any;
            if (anyData.playerInputs) {
                console.warn("收到旧格式的GameFrame数据，包含playerInputs字段。请更新后端接口以支持新的playerPositions格式。");
            } else {
                console.warn("GameFrame数据中没有playerPositions字段:", gameFrameData);
            }
        }
    }

    /**
     * 应用后端返回的玩家位置信息
     */
    private applyPlayerPositions(playerPositions: PlayerPositions[]): void {
        if (!playerPositions || !Array.isArray(playerPositions)) {
            console.warn("playerPositions 数据无效:", playerPositions);
            return;
        }

        const currentTime = Date.now();

        for (const playerPos of playerPositions) {
            if (!playerPos || !playerPos.userId || !playerPos.positions) {
                console.warn("玩家位置数据无效:", playerPos);
                continue;
            }

            const animalNode = this.allPlayerAnimals.get(playerPos.userId);
            if (!animalNode) continue;

            // 跳过已掉落或正在掉落的玩家，不更新其位置（避免干扰掉落动画）
            // 但是要确保方块动画不受影响，只跳过位置更新
            if (this.fallenPlayers.has(playerPos.userId) || this.fallingPlayers.has(playerPos.userId)) {
                // 已掉落或正在掉落的玩家不更新位置，但不影响方块动画的播放
                console.log(`🔒 位置锁定 - 跳过玩家${playerPos.userId}的位置更新（正在掉落中）`);
                continue;
            }

            // 处理该玩家的所有位置数据
            if (Array.isArray(playerPos.positions)) {
                for (const posData of playerPos.positions) {
                    if (posData && typeof posData.x === 'number' && typeof posData.y === 'number') {
                        // 直接使用后端返回的权威位置信息
                        this.applyServerPosition(animalNode, playerPos.userId, posData, currentTime);
                    }
                }
            }
        }
    }

    /**
     * 应用后端权威位置到玩家动物节点（平滑移动版本）
     */
    private applyServerPosition(animalNode: cc.Node, userId: string, posData: PositionData, timestamp: number): void {
        const serverPosition = cc.v2(posData.x, posData.y);

        // 🔒 如果玩家正在掉落，不进行任何位置更新或和解操作
        if (this.fallingPlayers.has(userId)) {
            return;
        }

        // 检查是否是本地玩家（需要特殊处理）
        const isLocalPlayer = (this.myPlayerData && userId === this.myPlayerData.userId);

        if (isLocalPlayer) {
            // 本地玩家：只进行服务器和解检查，不改变当前位置（除非误差过大）
            // 记录最新的服务器位置，供和解期间的误差校正使用
            this.lastServerPosition = serverPosition.clone();
            // 记录本地与服务器的位置差，便于定位回滚误差来源
            if (this.myAnimalNode && cc.isValid(this.myAnimalNode)) {
                const lp = this.myAnimalNode.getPosition();
                const dx = serverPosition.x - lp.x;
                const dy = serverPosition.y - lp.y;
                const dist = Math.sqrt(dx * dx + dy * dy);
                const now = Date.now();
                if (dist > 1 && now - this.lastErrorLogTime > 200) {
                    this.lastErrorLogTime = now;
                }
            }
            this.handleLocalPlayerReconciliation(animalNode, serverPosition, userId);
        } else {
            // 其他玩家
            if (this.useRenderInterpolationForRemotes) {
                // 采用渲染插值：不再设置 targetPosition，避免与插值系统打架
                // 仅依赖逻辑状态历史进行插值渲染
            } else {
                // 采用目标追赶平滑策略
                if (this.weakNetworkMode) {
                    this.handleWeakNetworkPlayerPosition(userId, serverPosition);
                } else {
                    this.setTargetPosition(userId, serverPosition);
                }
            }
        }

        // 更新逻辑状态用于渲染插值（对于其他玩家）
        if (!isLocalPlayer) {
            const velocity = this.calculateVelocityFromPosition(userId, posData);
            this.updateLogicState(userId, serverPosition, velocity);

            // 更新动画状态
            const isMoving = velocity.mag() > 0.1;
            this.updatePlayerAnimationState(userId, isMoving);
        }
    }

    // 和解状态管理
    private reconciliationStates: Map<string, boolean> = new Map();

    /**
     * 为其他玩家设置目标位置，启动平滑插值移动
     */
    private setTargetPosition(userId: string, targetPosition: cc.Vec2): void {
        this.targetPositions.set(userId, targetPosition.clone());
        this.playerMovingStates.set(userId, true);
    }

    /**
     * 弱网环境下处理其他玩家位置 - 防止瞬移
     */
    private handleWeakNetworkPlayerPosition(userId: string, serverPosition: cc.Vec2): void {
        const animalNode = this.allPlayerAnimals.get(userId);
        if (!animalNode) return;

        // 获取或初始化位置缓冲区
        let positionBuffer = this.playerPositionBuffers.get(userId);
        if (!positionBuffer) {
            positionBuffer = [];
            this.playerPositionBuffers.set(userId, positionBuffer);
        }

        // 添加新位置到缓冲区
        positionBuffer.push(serverPosition.clone());
        if (positionBuffer.length > this.positionBufferSize) {
            positionBuffer.shift();
        }

        const currentPosition = animalNode.getPosition();
        const distance = cc.Vec2.distance(cc.v2(currentPosition.x, currentPosition.y), serverPosition);

        // 根据距离选择不同策略
        if (distance > 100) {
            // 距离过大，可能是网络延迟导致的跳跃，使用外推预测
            this.applyExtrapolationForPlayer(userId, animalNode, positionBuffer);
        } else if (distance > 30) {
            // 中等距离，使用缓冲平滑
            this.applyBufferedSmoothingForPlayer(userId, animalNode, positionBuffer);
        } else {
            // 距离较小，正常插值
            this.setTargetPosition(userId, serverPosition);
        }
    }

    /**
     * 为玩家应用外推预测 - 解决大幅跳跃
     */
    private applyExtrapolationForPlayer(userId: string, animalNode: cc.Node, positionBuffer: cc.Vec2[]): void {
        if (positionBuffer.length < 2) {
            // 数据不足，直接使用最新位置
            const latestPos = positionBuffer[positionBuffer.length - 1];
            this.setTargetPosition(userId, latestPos);
            return;
        }

        // 计算移动趋势
        const latest = positionBuffer[positionBuffer.length - 1];
        const previous = positionBuffer[positionBuffer.length - 2];

        const velocity = this.getVec2FromPool(
            latest.x - previous.x,
            latest.y - previous.y
        );

        // 基于趋势预测下一个位置
        const predictedPosition = this.getVec2FromPool(
            latest.x + velocity.x * 0.5, // 预测半步
            latest.y + velocity.y * 0.5
        );

        // 使用预测位置作为目标
        this.setTargetPosition(userId, predictedPosition);

        // 返回向量到对象池
        this.returnVec2ToPool(velocity);
        this.returnVec2ToPool(predictedPosition);
    }

    /**
     * 为玩家应用缓冲平滑 - 减少抖动
     */
    private applyBufferedSmoothingForPlayer(userId: string, animalNode: cc.Node, positionBuffer: cc.Vec2[]): void {
        if (positionBuffer.length < 2) {
            const latestPos = positionBuffer[positionBuffer.length - 1];
            this.setTargetPosition(userId, latestPos);
            return;
        }

        // 计算缓冲区中位置的平均值
        let avgX = 0, avgY = 0;
        for (const pos of positionBuffer) {
            avgX += pos.x;
            avgY += pos.y;
        }
        avgX /= positionBuffer.length;
        avgY /= positionBuffer.length;

        const smoothedPosition = this.getVec2FromPool(avgX, avgY);
        this.setTargetPosition(userId, smoothedPosition);

        this.returnVec2ToPool(smoothedPosition);
    }

    /**
     * 处理本地玩家的服务器和解 - 优化版
     */
    private handleLocalPlayerReconciliation(animalNode: cc.Node, serverPosition: cc.Vec2, userId: string): void {
        const currentPosition = animalNode.getPosition();
        const distance = cc.Vec2.distance(cc.v2(currentPosition.x, currentPosition.y), serverPosition);
        const currentTime = Date.now();

        // 动态调整和解阈值
        this.updateAdaptiveThreshold();

        // 检查是否已经在和解状态
        const isAlreadyReconciling = this.reconciliationStates.get(userId) || false;

        // 防止频繁和解：最小间隔1200ms，进一步减少回滚频率
        const timeSinceLastReconciliation = currentTime - this.lastReconciliationTime;
        const minReconciliationInterval = 1200;

        // 连续超过阈值的计数（降低偶发尖峰触发回滚）
        if (distance > this.adaptiveThreshold) {
            this.consecutiveOverThresholdFrames++;
        } else {
            this.consecutiveOverThresholdFrames = 0;
        }

        // 检查是否过于频繁和解，如果是则暂时禁用
        const veryRecentReconciliations = this.reconciliationHistory.filter(time =>
            currentTime - time < 3000 // 3秒内
        );

        // 在“碰撞跟随模式”下，放宽/跳过触发门槛，立即进入和解，严格跟随后端
        if (this.isCollisionFollowing && !isAlreadyReconciling) {
            this.startSmoothReconciliation(userId, serverPosition);
            this.lastReconciliationTime = currentTime;
        } else if (distance > this.adaptiveThreshold && !isAlreadyReconciling &&
            timeSinceLastReconciliation > minReconciliationInterval &&
            veryRecentReconciliations.length < 3 &&
            this.consecutiveOverThresholdFrames >= 3) { // 连续3帧超过阈值才触发

            // 检查是否频繁和解，如果是则提高阈值
            this.recordReconciliation();

            // 启动平滑和解过程
            this.startSmoothReconciliation(userId, serverPosition);
            this.lastReconciliationTime = currentTime;
        } else if (distance <= 6 && isAlreadyReconciling) {
            // 更严格的停止条件，确保精确对齐
            this.reconciliationStates.delete(userId);
            this.playerMovingStates.set(userId, false);
            // 若已离开碰撞状态，也结束碰撞跟随模式
            if (!this.checkLocalCollision(currentPosition.x, currentPosition.y)) {
                this.isCollisionFollowing = false;
                this.collisionReleaseFrames = 0;
            }
        }

        // 更新预测置信度和网络延迟补偿
        this.updatePredictionConfidence(distance);
        this.updateNetworkDelayCompensation();
    }

    /**
     * 启动平滑服务器和解过程
     */
    private startSmoothReconciliation(userId: string, serverPosition: cc.Vec2): void {
        // 对于本地玩家，设置目标位置进行平滑和解
        this.targetPositions.set(userId, serverPosition.clone());
        this.playerMovingStates.set(userId, true);
        // 标记为和解状态，使用更高的移动速度
        this.reconciliationStates.set(userId, true);
        const node = this.allPlayerAnimals.get(userId);
        if (node) {
            const p = node.getPosition();
        }
    }

    /**
     * 动态调整和解阈值 - 激进提升版本
     */
    private updateAdaptiveThreshold(): void {
        const recentReconciliations = this.reconciliationHistory.filter(time =>
            Date.now() - time < 10000 // 扩展到10秒内
        );

        // 根据最近的和解频率激进调整阈值
        if (recentReconciliations.length > 3) {
            // 频繁和解时大幅提高阈值
            this.adaptiveThreshold = Math.min(this.maxReconciliationThreshold, this.adaptiveThreshold + 5);
        } else if (recentReconciliations.length > 1) {
            // 中等频率时适度提高阈值
            this.adaptiveThreshold = Math.min(this.maxReconciliationThreshold, this.adaptiveThreshold + 2);
        } else if (recentReconciliations.length === 0) {
            // 长时间无和解时才缓慢降低阈值（最低不低于20）
            this.adaptiveThreshold = Math.max(20, this.adaptiveThreshold - 1);
        }
    }

    /**
     * 更新预测置信度
     */
    private updatePredictionConfidence(distance: number): void {
        // 根据与服务器位置的偏差调整预测置信度
        if (distance < 5) {
            this.predictionConfidence = Math.min(1.0, this.predictionConfidence + 0.02);
        } else if (distance > 20) {
            this.predictionConfidence = Math.max(0.3, this.predictionConfidence - 0.05);
        }
    }

    /**
     * 更新网络延迟补偿
     */
    private updateNetworkDelayCompensation(): void {
        // 基于网络抖动历史计算延迟补偿
        if (this.jitterCount > 3) {
            const avgJitter = this.jitterSum / this.jitterCount;
            this.networkDelayCompensation = Math.min(50, avgJitter * 0.5);
        }
    }

    /**
     * 应用预测补偿 - 智能误差控制版本
     */
    private applyPredictionCompensation(): cc.Vec2 {
        const baseVelocity = this.currentPlayerVelocity;

        // 【新增】基于当前误差动态调整预测强度
        let predictionFactor = 1.0;

        // 如果当前与服务器位置差距较大，减少预测补偿，避免误差进一步累积
        if (this.lastServerPosition && this.myAnimalNode && cc.isValid(this.myAnimalNode)) {
            const currentPos = this.myAnimalNode.getPosition();
            const distance = cc.Vec2.distance(
                cc.v2(currentPos.x, currentPos.y),
                this.lastServerPosition
            );

            // 根据距离动态调整预测因子
            if (distance > 20) {
                predictionFactor = 0.7; // 大误差时减少预测
            } else if (distance > 10) {
                predictionFactor = 0.85; // 中等误差时适度减少
            } else if (distance > 5) {
                predictionFactor = 0.95; // 小误差时轻微减少
            }
        }

        // 在弱网模式下，适度增强本地预测以减少延迟感（但受误差控制限制）
        if (this.weakNetworkMode && this.extrapolationEnabled) {
            // 根据网络质量调整预测强度，但不超过误差控制的限制
            let networkFactor = 1.0;
            switch (this.networkQuality) {
                case 'bad':
                    networkFactor = 1.2; // 降低从1.3到1.2
                    break;
                case 'poor':
                    networkFactor = 1.1; // 降低从1.15到1.1
                    break;
                default:
                    networkFactor = 1.0;
            }

            // 取两个因子的较小值，优先控制误差
            predictionFactor = Math.min(predictionFactor, networkFactor);
        }

        return this.getVec2FromPool(
            baseVelocity.x * predictionFactor,
            baseVelocity.y * predictionFactor
        );
    }

    /**
     * 根据位置变化计算速度（用于动画状态判断）
     */
    private calculateVelocityFromPosition(userId: string, currentPos: PositionData): cc.Vec2 {
        const lastState = this.getLatestLogicState(userId);
        if (!lastState) {
            return cc.Vec2.ZERO;
        }

        const deltaX = currentPos.x - lastState.position.x;
        const deltaY = currentPos.y - lastState.position.y;

        // 计算速度（像素/秒）
        const velocity = cc.v2(deltaX / this.fixedDeltaTime, deltaY / this.fixedDeltaTime);
        return velocity;
    }





    /**
     * 更新动物朝向（根据移动方向旋转）- 添加精度控制
     */
    private updateAnimalRotation(animalNode: cc.Node, velocity: cc.Vec2, dt: number): void {
        // 只有在有移动时才更新朝向
        if (velocity.mag() < 0.1) return;

        // 【关键】应用精度控制到速度分量
        const preciseVelX = this.roundToPrecision(velocity.x);
        const preciseVelY = this.roundToPrecision(velocity.y);

        // 计算目标角度（弧度）
        const targetAngle = Math.atan2(preciseVelY, preciseVelX);

        // 转换为度数，并调整为动物预制体的默认朝向（向上）
        // 预制体默认向上，所以需要减去90度来校正
        const targetDegree = this.roundToPrecision((targetAngle * (180 / Math.PI)) - 90);

        // 获取当前角度并应用精度控制
        let currentDegree = this.roundToPrecision(animalNode.angle);

        // 计算角度差值（处理360度跨越）
        let angleDiff = targetDegree - currentDegree;

        // 将角度差值规范化到[-180, 180]范围
        while (angleDiff > 180) angleDiff -= 360;
        while (angleDiff < -180) angleDiff += 360;

        // 平滑旋转（避免突然跳跃）
        // rotationSmoothSpeed 视为“度/秒”，按真实 dt 推进，去除60FPS基准硬编码
        const rotationSpeed = this.rotationSmoothSpeed * dt;
        const maxRotationThisFrame = this.roundToPrecision(rotationSpeed);

        if (Math.abs(angleDiff) > maxRotationThisFrame) {
            // 需要分多帧旋转
            const rotationDirection = angleDiff > 0 ? 1 : -1;
            const newAngle = this.roundToPrecision(currentDegree + maxRotationThisFrame * rotationDirection);
            animalNode.angle = newAngle;
        } else {
            // 可以一次到位
            animalNode.angle = targetDegree;
        }

        // 记录当前移动方向（应用精度控制）
        const normalizedVel = cc.v2(preciseVelX, preciseVelY).normalize();
        this.lastMovementDirection = cc.v2(
            this.roundToPrecision(normalizedVel.x),
            this.roundToPrecision(normalizedVel.y)
        );
    }

    /**
     * 根据动物节点获取玩家ID
     */
    private getPlayerIdByAnimalNode(animalNode: cc.Node): string | null {
        // 使用传统的forEach来避免迭代器兼容性问题
        let foundUserId: string | null = null;
        this.allPlayerAnimals.forEach((node, userId) => {
            if (node === animalNode) {
                foundUserId = userId;
            }
        });
        return foundUserId;
    }

    /**
     * 获取玩家最新的逻辑状态
     */
    private getLatestLogicState(userId: string): LogicState | null {
        const states = this.logicStates.get(userId);
        return states && states.length > 0 ? states[states.length - 1] : null;
    }

    /**
     * 更新玩家的逻辑状态
     */
    private updateLogicState(userId: string, position: cc.Vec2, velocity: cc.Vec2): void {
        if (!this.logicStates.has(userId)) {
            this.logicStates.set(userId, []);
        }

        const states = this.logicStates.get(userId);
        const newState: LogicState = {
            position: position.clone(),
            velocity: velocity.clone(),
            timestamp: Date.now(),
            logicFrame: this.currentLogicFrame
        };

        states.push(newState);

        // 限制历史状态数量
        if (states.length > this.maxLogicStateHistory) {
            states.shift();
        }
    }

    /**
     * 渲染插值更新 - 核心丝滑功能 + 抖动缓冲
     * 在20FPS逻辑帧之间进行平滑插值，让60/120FPS渲染变得丝滑
     * 使用渲染延迟来缓冲网络抖动，确保渲染的稳定性
     */
    // 渲染插值缓存（性能优化）- 增强版
    private lastRenderTime: number = 0;
    private readonly MIN_RENDER_INTERVAL: number = 8; // 最小渲染间隔(毫秒)，约120FPS

    // 帧率自适应机制
    private frameTimeHistory: number[] = []; // 帧时间历史
    private maxFrameTimeHistory: number = 30; // 最多记录30帧
    private adaptiveRenderInterval: number = 8; // 自适应渲染间隔
    private lastFrameTime: number = 0; // 上一帧时间
    private lastAvgFrameTime: number = 16; // 最近一段时间的平均帧时间（ms），用于低FPS优化

    // 插值计算缓存
    private interpolationCache: Map<string, { position: cc.Vec2, velocity: cc.Vec2, timestamp: number }> = new Map();
    private cacheValidDuration: number = 16; // 缓存有效期16ms（约60FPS）

    private updateRenderInterpolation(dt: number): void {
        const currentTime = Date.now();

        // 帧率自适应机制
        this.updateAdaptiveFrameRate(currentTime);

        // 性能优化：使用自适应渲染间隔
        if (currentTime - this.lastRenderTime < this.adaptiveRenderInterval) {
            return;
        }
        this.lastRenderTime = currentTime;

        // 计算渲染时间点 - 总是比当前时间早一个缓冲时间
        const renderTime = currentTime - this.adaptiveRenderDelay;

        this.allPlayerAnimals.forEach((animalNode, userId) => {
            // 如果启用远端插值，仅对远端玩家生效
            const isLocalPlayer = (this.myPlayerData && userId === this.myPlayerData.userId);
            if (isLocalPlayer || !this.useRenderInterpolationForRemotes) {
                return;
            }
            const logicStates = this.logicStates.get(userId);
            if (!logicStates || logicStates.length === 0) return;

            // 跳过已掉落或正在掉落的玩家，不更新其位置（避免干扰掉落动画）
            if (this.fallenPlayers.has(userId) || this.fallingPlayers.has(userId)) {
                return;
            }

            // 获取当前渲染状态
            let renderState = this.renderStates.get(userId);
            if (!renderState) {
                // 初始化渲染状态
                const latestLogic = logicStates[logicStates.length - 1];
                renderState = {
                    position: latestLogic.position.clone(),
                    velocity: latestLogic.velocity.clone(),
                    lastLogicFrame: latestLogic.logicFrame
                };
                this.renderStates.set(userId, renderState);
            }

            // 执行带缓冲的渲染插值（使用本帧渲染dt确保旋转速度与本地一致）
            this.performBufferedRenderInterpolation(animalNode, userId, renderState, logicStates, renderTime, dt);
        });

        // 更新自适应渲染延迟
        this.updateAdaptiveRenderDelay();
    }

    /**
     * 帧率自适应机制 - 根据设备性能动态调整渲染间隔
     */
    private updateAdaptiveFrameRate(currentTime: number): void {
        if (this.lastFrameTime > 0) {
            const frameTime = currentTime - this.lastFrameTime;
            this.frameTimeHistory.push(frameTime);

            // 限制历史记录大小
            if (this.frameTimeHistory.length > this.maxFrameTimeHistory) {
                this.frameTimeHistory.shift();
            }

            // 计算平均帧时间
            if (this.frameTimeHistory.length >= 10) {
                const avgFrameTime = this.frameTimeHistory.reduce((sum, time) => sum + time, 0) / this.frameTimeHistory.length;
                this.lastAvgFrameTime = avgFrameTime;

                // 根据平均帧时间调整渲染间隔
                if (avgFrameTime > 20) { // 低于50FPS
                    this.adaptiveRenderInterval = Math.min(16, this.adaptiveRenderInterval + 1);
                } else if (avgFrameTime < 12) { // 高于83FPS
                    this.adaptiveRenderInterval = Math.max(6, this.adaptiveRenderInterval - 1);
                }
            }
        }
        this.lastFrameTime = currentTime;
    }

    /**
     * 执行带缓冲的渲染插值计算 - 抖动缓冲的核心实现
     */
    private performBufferedRenderInterpolation(
        animalNode: cc.Node,
        userId: string,
        renderState: RenderState,
        logicStates: LogicState[],
        renderTime: number,
        dtRender: number
    ): void {
        const currentTime = Date.now();
        // 找到适合插值的两个逻辑状态
        let prevState: LogicState = null;
        let nextState: LogicState = null;

        // 记录找到区间时的索引，便于进行Catmull-Rom插值
        let prevIndex: number = -1;
        let nextIndex: number = -1;

        // 寻找渲染时间点应该在哪两个逻辑帧之间
        for (let i = 0; i < logicStates.length - 1; i++) {
            const state1 = logicStates[i];
            const state2 = logicStates[i + 1];

            if (state1.timestamp <= renderTime && state2.timestamp >= renderTime) {
                prevState = state1;
                nextState = state2;
                prevIndex = i;
                nextIndex = i + 1;
                break;
            }
        }

        // 如果没找到合适的区间，尝试使用最佳可用状态
        if (!prevState || !nextState) {
            const bestState = this.findBestStateForRenderTime(logicStates, renderTime);
            if (bestState) {
                animalNode.x = bestState.position.x;
                animalNode.y = bestState.position.y;

                // 更新渲染状态
                renderState.position = bestState.position.clone();
                renderState.velocity = bestState.velocity.clone();

                // 更新朝向
                if (bestState.velocity.mag() > 0.01) {
                    this.updateAnimalRotation(animalNode, bestState.velocity, dtRender);
                }
            }
            return;
        }

        // 计算插值进度
        const timeDiff = nextState.timestamp - prevState.timestamp;
        const progress = timeDiff > 0 ? (renderTime - prevState.timestamp) / timeDiff : 0;
        const clampedProgress = Math.max(0, Math.min(1, progress));

        // 检查插值缓存
        const cacheKey = `${userId}_${prevState.timestamp}_${nextState.timestamp}`;
        const cachedResult = this.interpolationCache.get(cacheKey);

        if (cachedResult && (currentTime - cachedResult.timestamp) < this.cacheValidDuration) {
            // 使用缓存结果
            animalNode.x = cachedResult.position.x;
            animalNode.y = cachedResult.position.y;

            // 更新朝向（如果有移动）
            if (cachedResult.velocity.mag() > 0.01) {
                this.updateAnimalRotation(animalNode, cachedResult.velocity, dtRender);
            }

            // 更新渲染状态
            renderState.position.x = cachedResult.position.x;
            renderState.position.y = cachedResult.position.y;
            renderState.velocity = cachedResult.velocity;
            return;
        }

        // 性能优化：减少向量创建和计算
        // 位置插值：优先使用Catmull-Rom（四点）以在低FPS时获得更平滑曲线，否则使用线性
        let targetX: number;
        let targetY: number;
        if (prevIndex > 0 && nextIndex < logicStates.length - 1) {
            const p0 = logicStates[prevIndex - 1].position;
            const p1 = prevState.position;
            const p2 = nextState.position;
            const p3 = logicStates[nextIndex + 1].position;
            targetX = this.catmullRom(p0.x, p1.x, p2.x, p3.x, clampedProgress);
            targetY = this.catmullRom(p0.y, p1.y, p2.y, p3.y, clampedProgress);
        } else {
            targetX = prevState.position.x + (nextState.position.x - prevState.position.x) * clampedProgress;
            targetY = prevState.position.y + (nextState.position.y - prevState.position.y) * clampedProgress;
        }

        // 低FPS下的额外混合：将上帧渲染位置与目标位置做一次平滑，降低单帧跳变
        let blendAlpha = 1.0;
        if (this.lastAvgFrameTime > 28) { // ~35FPS或更低
            blendAlpha = 0.6;
        } else if (this.lastAvgFrameTime > 20) { // ~50FPS附近
            blendAlpha = 0.85;
        }
        const prevRenderX = renderState.position.x;
        const prevRenderY = renderState.position.y;
        const blendedX = this.roundToPrecision(prevRenderX + (targetX - prevRenderX) * blendAlpha);
        const blendedY = this.roundToPrecision(prevRenderY + (targetY - prevRenderY) * blendAlpha);
        animalNode.x = blendedX;
        animalNode.y = blendedY;

        // 速度插值（性能优化：只在需要时计算）
        const velX = this.roundToPrecision(prevState.velocity.x + (nextState.velocity.x - prevState.velocity.x) * clampedProgress);
        const velY = this.roundToPrecision(prevState.velocity.y + (nextState.velocity.y - prevState.velocity.y) * clampedProgress);
        const velocityMagnitude = Math.sqrt(velX * velX + velY * velY);

        // 从对象池获取速度向量
        const interpolatedVelocity = velocityMagnitude > 0.01 ? this.getVec2FromPool(velX, velY) : this.getVec2FromPool(0, 0);

        // 更新朝向（如果有移动）
        if (velocityMagnitude > 0.01) {
            this.updateAnimalRotation(animalNode, interpolatedVelocity, dtRender);
        }

        // 缓存计算结果（缓存最终用于渲染的混合位置）
        const cachedPos = this.getVec2FromPool(blendedX, blendedY);
        const cachedVel = this.getVec2FromPool(velX, velY);
        this.interpolationCache.set(cacheKey, {
            position: cachedPos,
            velocity: cachedVel,
            timestamp: currentTime
        });

        // 限制缓存大小
        if (this.interpolationCache.size > 100) {
            const oldestKey = this.interpolationCache.keys().next().value;
            const oldResult = this.interpolationCache.get(oldestKey);
            if (oldResult) {
                this.returnVec2ToPool(oldResult.position);
                this.returnVec2ToPool(oldResult.velocity);
            }
            this.interpolationCache.delete(oldestKey);
        }

        // 更新渲染状态（性能优化：重用已计算的位置向量）
        renderState.position.x = blendedX;
        renderState.position.y = blendedY;
        // 返回旧的速度向量到对象池
        if (renderState.velocity) {
            this.returnVec2ToPool(renderState.velocity);
        }
        renderState.velocity = interpolatedVelocity;
    }

    // Catmull-Rom 样条插值（标量版本）
    private catmullRom(p0: number, p1: number, p2: number, p3: number, t: number): number {
        const t2 = t * t;
        const t3 = t2 * t;
        return 0.5 * (
            (2 * p1) +
            (-p0 + p2) * t +
            (2 * p0 - 5 * p1 + 4 * p2 - p3) * t2 +
            (-p0 + 3 * p1 - 3 * p2 + p3) * t3
        );
    }



    /**
     * 更新自适应渲染延迟 - 优化版网络抖动算法
     */
    private updateAdaptiveRenderDelay(): void {
        if (this.jitterCount < 3) {
            // 数据不足，使用默认延迟
            return;
        }

        // 使用平均值而非排序，提升性能
        const avgJitter = this.jitterSum / this.jitterCount;

        // 计算新的自适应延迟（简化算法）
        const baseDelay = this.renderDelay;
        const adaptiveDelay = Math.max(baseDelay, Math.min(avgJitter * 1.5, this.jitterBufferSize));

        // 平滑调整，避免突然变化
        const smoothingFactor = 0.15; // 稍微提高响应速度
        this.adaptiveRenderDelay = this.adaptiveRenderDelay * (1 - smoothingFactor) + adaptiveDelay * smoothingFactor;

        // 确保延迟在合理范围内
        this.adaptiveRenderDelay = Math.max(40, Math.min(this.adaptiveRenderDelay, this.jitterBufferSize));
    }

    /**
     * 记录网络抖动数据 - 弱网优化版
     */
    private recordNetworkJitter(): void {
        const currentTime = Date.now();

        // 更新包统计
        this.totalPackets++;

        if (this.lastFrameReceiveTime > 0) {
            const interval = currentTime - this.lastFrameReceiveTime;

            // 🎯 开局前5帧忽略异常间隔，避免初始化时的大间隔影响网络质量判断
            const isEarlyFrame = this.totalPackets <= 5;

            // 检测丢包（间隔过长）：放宽为 > 4 倍发送间隔，避免把合并/黏包误判为丢包
            // 开局前5帧使用更宽松的丢包检测（10倍间隔）
            const missThreshold = isEarlyFrame ? this.batchSendInterval * 10 : this.batchSendInterval * 4;
            const isMiss = interval > missThreshold ? 1 : 0;
            this.packetWindow.push(isMiss);
            if (this.packetWindow.length > this.packetWindowSize) {
                this.packetWindow.shift();
            }

            // 记录延迟 - 开局前5帧限制最大延迟，避免初始化延迟影响
            const recordedInterval = isEarlyFrame ? Math.min(interval, this.batchSendInterval * 3) : interval;
            this.latencyHistory.push(recordedInterval);
            if (this.latencyHistory.length > 20) {
                this.latencyHistory.shift();
            }

            // 计算平均延迟
            this.averageLatency = this.latencyHistory.reduce((sum, lat) => sum + lat, 0) / this.latencyHistory.length;

            // 使用固定窗口计算丢包率，避免长期累积导致"黏住坏"
            const windowSum = this.packetWindow.reduce((sum, v) => sum + v, 0);
            this.packetLossRate = this.packetWindow.length > 0 ? windowSum / this.packetWindow.length : 0;

            // 计算抖动（与期望的50ms间隔的差异）- 开局前5帧限制最大抖动
            const expectedInterval = this.batchSendInterval;
            const rawJitter = Math.abs(interval - expectedInterval);
            const jitter = isEarlyFrame ? Math.min(rawJitter, expectedInterval * 2) : rawJitter;

            // 使用滑动平均，避免数组操作
            this.jitterSum += jitter;
            this.jitterCount++;

            // 限制历史记录数量，使用滑动窗口
            if (this.jitterCount > this.maxJitterHistorySize) {
                // 重置计数器，保持最近的平均值
                this.jitterSum = this.jitterSum * 0.8; // 保留80%的历史影响
                this.jitterCount = Math.floor(this.jitterCount * 0.8);
            }

            // 保留少量历史记录用于其他计算
            this.networkJitterHistory.push(jitter);
            if (this.networkJitterHistory.length > 5) {
                this.networkJitterHistory.shift();
            }
        }

        this.lastFrameReceiveTime = currentTime;

        // 评估网络质量并调整策略
        this.evaluateNetworkQuality();
    }

    /**
     * 评估网络质量并启用相应策略
     */
    private evaluateNetworkQuality(): void {
        const avgJitter = this.jitterCount > 0 ? this.jitterSum / this.jitterCount : 0;

        // 🎯 开局前10帧使用更宽松的网络质量评估，避免初始化影响
        const isEarlyGame = this.totalPackets <= 10;

        if (isEarlyGame) {
            // 开局阶段：使用更宽松的阈值，避免初始化时的异常数据影响判断
            if (this.packetLossRate > 0.3 || this.averageLatency > 500 || avgJitter > 300) {
                this.networkQuality = 'bad';
            } else if (this.packetLossRate > 0.2 || this.averageLatency > 300 || avgJitter > 200) {
                this.networkQuality = 'poor';
            } else {
                this.networkQuality = 'good';
            }

        } else {
            // 正常游戏阶段：使用标准阈值
            if (this.packetLossRate > 0.15 || this.averageLatency > 250 || avgJitter > 150) {
                this.networkQuality = 'bad';
            } else if (this.packetLossRate > 0.08 || this.averageLatency > 150 || avgJitter > 80) {
                this.networkQuality = 'poor';
            } else {
                this.networkQuality = 'good';
            }
        }

        // 根据网络质量调整策略
        this.adjustStrategiesForNetworkQuality();
    }

    /**
     * 根据网络质量调整策略
     */
    private adjustStrategiesForNetworkQuality(): void {
        switch (this.networkQuality) {
            case 'bad':
                this.weakNetworkMode = true;
                this.extrapolationEnabled = true;
                this.adaptiveThreshold = 95; // 更高的和解阈值，降低频繁回滚
                this.reconciliationSmoothness = 0.03; // 更平滑的校正
                this.positionBufferSize = 8; // 增大缓冲区
                break;

            case 'poor':
                this.weakNetworkMode = true;
                this.extrapolationEnabled = true;
                this.adaptiveThreshold = 60; // 适度提高阈值
                this.reconciliationSmoothness = 0.04;
                this.positionBufferSize = 6;
                break;

            case 'good':
                this.weakNetworkMode = false;
                this.extrapolationEnabled = false;
                this.adaptiveThreshold = 20; // 正常网络下使用阈值20
                this.reconciliationSmoothness = 0.05;
                this.positionBufferSize = 5;
                break;
        }
    }

    /**
     * 如果需要，初始化玩家的逻辑状态
     */
    private initializeLogicStateIfNeeded(userId: string, animalNode: cc.Node): void {
        if (!this.logicStates.has(userId)) {
            const initialState: LogicState = {
                position: cc.v2(animalNode.x, animalNode.y),
                velocity: cc.Vec2.ZERO,
                timestamp: Date.now(),
                logicFrame: this.currentLogicFrame
            };
            this.logicStates.set(userId, [initialState]);
        }

        if (!this.renderStates.has(userId)) {
            const initialRenderState: RenderState = {
                position: cc.v2(animalNode.x, animalNode.y),
                velocity: cc.Vec2.ZERO,
                lastLogicFrame: this.currentLogicFrame
            };
            this.renderStates.set(userId, initialRenderState);
        }
    }

    /**
     * 初始化方块动画管理器
     */
    private initializeBlockAnimationManager(): void {
        if (!this.blockParent) {
            console.warn("方块父节点未设置，跳过方块动画管理器初始化");
            return;
        }

        this.blockAnimationManager = BlockAnimationManager.getInstance();
        this.blockAnimationManager.initialize(this.blockParent);


    }

    /**
     * 初始化游戏事件处理器
     */
    private initializeGameEventHandler(): void {
        this.gameEventHandler = new SimpleGameEventHandler();
        this.gameEventHandler.setGamePageController(this);
        this.gameEventHandler.registerWebSocketHandlers();

    }

    /**
     * 处理游戏事件消息（从WebSocket接收）
     * 根据API文档的统一数字事件系统处理
     */
    public handleGameEvent(eventData: GameEventData): void {


        // 如果方块动画管理器未初始化，尝试初始化
        if (!this.blockAnimationManager) {
            this.initializeBlockAnimationManager();
        }

        // 只有方块相关事件(3-5)才委托给方块动画管理器处理，避免玩家事件干扰方块动画
        if (eventData.eventType === 3 || eventData.eventType === 4 || eventData.eventType === 5) {
            if (this.blockAnimationManager) {
                this.blockAnimationManager.handleGameEvent(eventData);
            } else {
                console.warn("方块动画管理器未初始化，无法处理游戏事件");
            }
        }

        // 根据事件类型执行额外逻辑
        this.handleAdditionalEventLogic(eventData);
    }

    /**
     * 为渲染时间点找到最佳的逻辑状态
     */
    private findBestStateForRenderTime(logicStates: LogicState[], renderTime: number): LogicState | null {
        if (logicStates.length === 0) return null;

        // 如果渲染时间比所有状态都早，使用最早的状态
        if (renderTime <= logicStates[0].timestamp) {
            return logicStates[0];
        }

        // 如果渲染时间比所有状态都晚，使用最新的状态（但要小心外插值）
        const latestState = logicStates[logicStates.length - 1];
        if (renderTime >= latestState.timestamp) {
            // 检查时间差是否在可接受范围内
            const timeDiff = renderTime - latestState.timestamp;
            if (timeDiff <= this.jitterBufferSize) {
                return latestState;
            }
        }

        // 找到时间最接近的状态
        let bestState = logicStates[0];
        let minTimeDiff = Math.abs(renderTime - bestState.timestamp);

        for (const state of logicStates) {
            const timeDiff = Math.abs(renderTime - state.timestamp);
            if (timeDiff < minTimeDiff) {
                minTimeDiff = timeDiff;
                bestState = state;
            }
        }

        return bestState;
    }

    private handleAdditionalEventLogic(eventData: GameEventData): void {


        switch (eventData.eventType) {
            case 1: // GameEventWaiting - 等待阶段
                this.currentGamePhase = 1;

                this.handleCountdownStart(eventData);
                break;
            case 2: // GameEventRoundWaiting - 回合等待
                this.currentGamePhase = 2;

                this.handleCountdownEnd(eventData);
                break;
            case 3: // GameEventShaking - 松动阶段
                this.currentGamePhase = 3;
                if (eventData.blocks) {
                    this.updateFallenBlocks(eventData.blocks);
                }
                break;
            case 4: // GameEventFalling - 掉落阶段
                this.currentGamePhase = 4;
                if (eventData && eventData.blocks) {
                    this.updateFallenBlocks(eventData.blocks);
                }
                break;
            case 5: // GameEventRestoring - 补齐阶段
                this.currentGamePhase = 5;
                if (eventData && eventData.blocks) {
                    this.updateFallenBlocks(eventData.blocks);
                }
                break;
            case 6: // GameEventEnding - 结束阶段
                this.currentGamePhase = 6;
                break;
            case 7: // GameEventPlayerDeath - 玩家死亡
                this.handlePlayerStatusEvent(eventData);
                break;
            case 8: // GameEventPlayerDisconnect - 玩家断线
                this.handlePlayerStatusEvent(eventData);
                break;
            case 9: // GameEventPlayerLeave - 离开游戏
                this.handlePlayerStatusEvent(eventData);
                break;
            case 10: // GameEventPlayerReconnect - 断线重连
                this.handlePlayerStatusEvent(eventData);
                break;
            case 11: // GameEventShowcase - 难度升级展示
                this.handleShowcaseEvent(eventData);
                break;
            default:
                break;
        }
    }

    /**
     * 处理玩家状态事件（死亡、断线、退出、重连）
     * @param eventData 游戏事件数据
     */
    private handlePlayerStatusEvent(eventData: GameEventData): void {
        if (!eventData.userId) {
            console.warn("玩家状态事件缺少userId");
            return;
        }

        

        // 🎯 特殊处理eventType:10（重连事件）：根据isAlive字段决定显示状态
        if (eventData.eventType === 10) {
            if (this.gameLayoutController) {
                // 先隐藏断线标识
                this.gameLayoutController.hideDisconnectStatus(eventData.userId);
                
                // 根据isAlive字段决定是否显示死亡标识
                if (eventData.isAlive === false) {
                    
                    this.gameLayoutController.showPlayerStatus(eventData.userId, 7); // 显示死亡
                } else {
                 
                    this.gameLayoutController.hideAllPlayerStatus(eventData.userId); // 隐藏所有状态
                }
            }
        } else {
            // 其他事件类型（7-死亡, 8-断线, 9-退出）正常处理
            if (this.gameLayoutController) {
                this.gameLayoutController.showPlayerStatus(eventData.userId, eventData.eventType);
            } else {
                console.warn("GameLayoutController未设置，无法显示玩家状态");
            }
        }
    }

    /**
     * 根据用户ID查找玩家头像控制器
     * @param userId 用户ID
     * @returns 玩家头像控制器或null
     */
    private findPlayerAvatarController(userId: string): GamePlayerController | null {


        for (let i = 0; i < this.playerAvatarControllers.length; i++) {
            const controller = this.playerAvatarControllers[i];
            if (controller) {
                const controllerUserId = controller.getUserId();
                if (controllerUserId === userId) {
                    return controller;
                }
            } else {

            }
        }
        return null;
    }

    /**
     * 根据OnlineStateEvent显示玩家状态（用于断线重连）
     * @param userId 用户ID
     * @param onlineStateEvent OnlineStateEvent值
     */
    public showPlayerStatusByOnlineStateEvent(userId: string, onlineStateEvent: number): void {


        // 通过GameLayoutController来管理玩家状态显示
        if (this.gameLayoutController) {
            this.gameLayoutController.showPlayerStatusByOnlineStateEvent(userId, onlineStateEvent);
        } else {
            console.warn("GameLayoutController未设置，无法显示玩家状态");
        }
    }

    /**
     * 初始化所有玩家头像状态节点
     */
    private initializeAllPlayerAvatarStatus(): void {
        for (const controller of this.playerAvatarControllers) {
            if (controller) {
                controller.initializeStatusNodes();
            }
        }
    }

    /**
     * 处理断线重连时的用户数据，根据OnlineStateEvent显示状态
     * @param users 用户数据数组（来自EnterRoom API）
     */
    public handleReconnectionUserData(users: any[]): void {

        // 通过GameLayoutController来处理断线重连数据
        if (this.gameLayoutController) {
            this.gameLayoutController.handleReconnectionUserData(users);
        } else {
            console.warn("GameLayoutController未设置，无法处理断线重连数据");
        }

        // 断线重连回来：如果本地玩家已死亡，则显示GameOver并保持禁用输入
        try {
            const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
            if (myUserId && Array.isArray(users)) {
                const me = users.find(u => u && u.userId === myUserId);
                // 后端约定：bpStatus 1=存活 2=死亡
                if (me && me.bpStatus === 2) {
                    // 标记为已掉落
                    this.fallenPlayers.add(myUserId);
                    // 禁用输入
                    this.isInputBlocked = true;
                    if (this.joystickController) {
                        this.joystickController.setInteractable(false);
                    }
                    // 显示GameOver（若为倒数第二个死亡，即场上仅剩1人生还，则不显示）
                    const alive = this.getAlivePlayerCount();
                    if (alive > 1) {
                        this.showGameOver();
                    }
                }
            }
        } catch (e) {
            console.warn("处理断线重连死亡状态时出错", e);
        }
    }

    /**
     * 测试方法：手动触发玩家状态显示（调试用）
     * 可在控制台调用：gamePage.testPlayerStatus("userId", 7)
     */
    public testPlayerStatus(userId: string, eventType: number): void {


        // 直接通过GameLayoutController测试
        if (this.gameLayoutController) {
            this.gameLayoutController.showPlayerStatus(userId, eventType);
        } else {
            console.warn("GameLayoutController未设置，无法测试玩家状态");
        }
    }

    /**
     * 调试方法：列出所有玩家头像控制器信息
     */
    public debugPlayerControllers(): void {


        // 通过GameLayoutController获取调试信息
        if (this.gameLayoutController) {
            this.gameLayoutController.debugPlayerControllers();
        } else {
            console.warn("GameLayoutController未设置，无法获取调试信息");
        }

        // 同时显示原有的数组信息（如果有的话）

    }

    /**
     * 测试方法：模拟GameFrame消息（调试用）
     */
    public testGameFrameEvent(eventType: number, userId: string): void {

        const mockGameFrameData = {
            serverFrameId: 1,
            serverTime: Date.now(),
            gameEvents: [{
                eventType: eventType,
                userId: userId,
                currentRound: 1,
                countDown: 0,
                randomSeed: 0,
                message: `测试事件${eventType}`
            }],
            playerPositions: []
        };

        this.handleGameFrame(mockGameFrameData);
    }

    /**
     * 手动强制解锁（调试用）。可在控制台调用：gamePage.forceUnlockInput()
     */
    public forceUnlockInput(): void {

        this.isCountdownActive = false;
        this.isWaitingPhase = false;
        this.isInputBlocked = false;
        if (this.countdownTimer) {
            clearTimeout(this.countdownTimer);
            this.countdownTimer = 0;
        }
        if (this.countdownNode) {
            this.countdownNode.active = false;
        }
        // 但如果本地玩家已死亡，则保持摇杆禁用状态
        if (this.joystickController && !this.isLocalPlayerDead()) {
            this.joystickController.setInteractable(true);
            // 摇杆启用时隐藏提示动画
            this.hideTishiAnimation();
        }

    }

    /**
     * 获取方块动画管理器实例（供外部调用）
     */
    public getBlockAnimationManager(): BlockAnimationManager {
        return this.blockAnimationManager;
    }

    /**
     * 清理资源时同时清理方块动画管理器
     */
    protected onDestroy(): void {
        // 清理方块动画管理器
        if (this.blockAnimationManager) {
            this.blockAnimationManager.cleanup();
            this.blockAnimationManager = null;
        }

        // 清理平滑移动系统
        this.targetPositions.clear();
        this.playerMovingStates.clear();
        this.reconciliationStates.clear();

        // 清理待机动画定时器
        this.clearIdleAnimationTimer();

        // 组件销毁时隐藏回合数显示
        this.hideRoundDisplay();
    }

    // ========== 玩家掉落检测系统 ==========

    /**
     * 格子坐标转像素坐标（格子中心）
     * 将格子坐标转换为对应格子中心的像素坐标
     */
    private gridToPixel(gridX: number, gridY: number): { pixelX: number, pixelY: number } {
        const pixelX = this.BOARD_CONFIG.LEFT_X + (gridX + 0.5) * this.BOARD_CONFIG.GRID_SIZE;
        const pixelY = this.BOARD_CONFIG.BOTTOM_Y + (gridY + 0.5) * this.BOARD_CONFIG.GRID_SIZE;
        return { pixelX, pixelY };
    }

    /**
     * 像素坐标转格子坐标
     * 大地图范围: 左下角(-359,-343) 到 右上角(359,375)
     * 9x9格子，每格79x79像素
     */
    private pixelToGrid(pixelX: number, pixelY: number): { gridX: number, gridY: number } {
        // 与后端一致：使用总宽/高除以9计算格子宽/高
        const totalWidth = this.BOARD_CONFIG.RIGHT_X - this.BOARD_CONFIG.LEFT_X;   // 718
        const totalHeight = this.BOARD_CONFIG.TOP_Y - this.BOARD_CONFIG.BOTTOM_Y;  // 718
        const gridWidth = totalWidth / this.BOARD_CONFIG.GRID_COUNT;   // 79.777...
        const gridHeight = totalHeight / this.BOARD_CONFIG.GRID_COUNT; // 79.777...

        const offsetX = pixelX - this.BOARD_CONFIG.LEFT_X;
        const offsetY = pixelY - this.BOARD_CONFIG.BOTTOM_Y;

        const gridX = Math.floor(offsetX / gridWidth);
        const gridY = Math.floor(offsetY / gridHeight);

        // 与后端一致：超出范围返回(-1, -1)
        if (gridX < 0 || gridX >= this.BOARD_CONFIG.GRID_COUNT || gridY < 0 || gridY >= this.BOARD_CONFIG.GRID_COUNT) {
            return { gridX: -1, gridY: -1 };
        }

        return { gridX, gridY };
    }

    /**
     * 检查玩家是否出了棋盘边界 - 修复2：85%位置判定
     */
    private isPlayerOutOfBounds(playerX: number, playerY: number): boolean {
        // 与后端一致：直接使用坐标与边界比较
        return (
            playerX < this.BOARD_CONFIG.LEFT_X ||
            playerX > this.BOARD_CONFIG.RIGHT_X ||
            playerY < this.BOARD_CONFIG.BOTTOM_Y ||
            playerY > this.BOARD_CONFIG.TOP_Y
        );
    }

    /**
     * 检查玩家90%体积是否在安全格子上 - 修复版本
     */
    private isPlayerOnSafeGround(playerX: number, playerY: number): boolean {
        const survivalSize = this.BOARD_CONFIG.PLAYER_SIZE * this.BOARD_CONFIG.SURVIVAL_RATIO; // 54像素
        const halfSurvivalSize = survivalSize / 2; // 27像素

        // 计算玩家90%体积的四个边界点
        const leftX = playerX - halfSurvivalSize;
        const rightX = playerX + halfSurvivalSize;
        const bottomY = playerY - halfSurvivalSize;
        const topY = playerY + halfSurvivalSize;

        // 获取90%体积四个角点覆盖的格子
        const corners = [
            this.pixelToGrid(leftX, bottomY),     // 左下角
            this.pixelToGrid(rightX, bottomY),    // 右下角
            this.pixelToGrid(leftX, topY),        // 左上角
            this.pixelToGrid(rightX, topY)        // 右上角
        ];



        // 收集所有被覆盖的格子（去重）
        const coveredGrids = new Set<string>();

        // 找到边界范围
        const minGridX = Math.min(...corners.map(c => c.gridX));
        const maxGridX = Math.max(...corners.map(c => c.gridX));
        const minGridY = Math.min(...corners.map(c => c.gridY));
        const maxGridY = Math.max(...corners.map(c => c.gridY));

        let totalOverlapArea = 0;
        let safeOverlapArea = 0;
        const survivalAreaSize = survivalSize * survivalSize; // 90%体积的总面积

        // 遍历边界范围内的所有格子
        for (let gridX = minGridX; gridX <= maxGridX; gridX++) {
            for (let gridY = minGridY; gridY <= maxGridY; gridY++) {
                // 检查格子是否在有效范围内
                if (gridX < 0 || gridX >= this.BOARD_CONFIG.GRID_COUNT ||
                    gridY < 0 || gridY >= this.BOARD_CONFIG.GRID_COUNT) {
                    // 超出棋盘的部分算作不安全区域
                    const overlapArea = this.calculateOverlapArea(gridX, gridY, playerX, playerY, halfSurvivalSize);
                    totalOverlapArea += overlapArea;
                    continue;
                }

                // 计算这个格子与玩家90%体积的重叠面积
                const overlapArea = this.calculateOverlapArea(gridX, gridY, playerX, playerY, halfSurvivalSize);
                if (overlapArea > 0) {
                    const gridKey = `${gridX},${gridY}`;
                    coveredGrids.add(gridKey);
                    totalOverlapArea += overlapArea;

                    // 如果这个格子是安全的，累加安全面积
                    if (!this.fallenBlocks.has(gridKey)) {
                        safeOverlapArea += overlapArea;
                    }
                }
            }
        }

        // 如果没有覆盖任何格子，说明玩家悬空
        if (totalOverlapArea === 0) {

            return false;
        }

        // 计算安全面积比例
        const safeRatio = safeOverlapArea / totalOverlapArea;
        const requiredSafeRatio = 0.5; // 需要85%的体积在安全区域


        if (safeRatio >= requiredSafeRatio) {

            return true;
        } else {

            return false;
        }
    }

    /**
     * 计算格子与玩家90%体积区域的重叠面积
     */
    private calculateOverlapArea(gridX: number, gridY: number, playerX: number, playerY: number, halfSurvivalSize: number): number {
        // 计算格子的像素边界
        const gridPixelLeft = this.BOARD_CONFIG.LEFT_X + gridX * this.BOARD_CONFIG.GRID_SIZE;
        const gridPixelRight = gridPixelLeft + this.BOARD_CONFIG.GRID_SIZE;
        const gridPixelBottom = this.BOARD_CONFIG.BOTTOM_Y + gridY * this.BOARD_CONFIG.GRID_SIZE;
        const gridPixelTop = gridPixelBottom + this.BOARD_CONFIG.GRID_SIZE;

        // 计算玩家90%体积的边界
        const playerLeft = playerX - halfSurvivalSize;
        const playerRight = playerX + halfSurvivalSize;
        const playerBottom = playerY - halfSurvivalSize;
        const playerTop = playerY + halfSurvivalSize;

        // 计算重叠区域的边界
        const overlapLeft = Math.max(gridPixelLeft, playerLeft);
        const overlapRight = Math.min(gridPixelRight, playerRight);
        const overlapBottom = Math.max(gridPixelBottom, playerBottom);
        const overlapTop = Math.min(gridPixelTop, playerTop);

        // 如果没有重叠，返回0
        if (overlapLeft >= overlapRight || overlapBottom >= overlapTop) {
            return 0;
        }

        // 计算重叠面积
        const overlapWidth = overlapRight - overlapLeft;
        const overlapHeight = overlapTop - overlapBottom;
        return overlapWidth * overlapHeight;
    }

    /**
     * 检查格子是否与玩家90%体积区域重叠
     */
    private isGridOverlappingWithSurvivalArea(gridX: number, gridY: number, playerX: number, playerY: number, halfSurvivalSize: number): boolean {
        // 计算格子的像素边界
        const gridPixelLeft = this.BOARD_CONFIG.LEFT_X + gridX * this.BOARD_CONFIG.GRID_SIZE;
        const gridPixelRight = gridPixelLeft + this.BOARD_CONFIG.GRID_SIZE;
        const gridPixelBottom = this.BOARD_CONFIG.BOTTOM_Y + gridY * this.BOARD_CONFIG.GRID_SIZE;
        const gridPixelTop = gridPixelBottom + this.BOARD_CONFIG.GRID_SIZE;

        // 计算玩家90%体积的边界
        const playerLeft = playerX - halfSurvivalSize;
        const playerRight = playerX + halfSurvivalSize;
        const playerBottom = playerY - halfSurvivalSize;
        const playerTop = playerY + halfSurvivalSize;

        // 检查是否有重叠（矩形相交检测）
        const hasOverlap = !(
            playerRight <= gridPixelLeft ||   // 玩家在格子左边
            playerLeft >= gridPixelRight ||   // 玩家在格子右边
            playerTop <= gridPixelBottom ||   // 玩家在格子下边
            playerBottom >= gridPixelTop      // 玩家在格子上边
        );

        return hasOverlap;
    }

    /**
     * 检查玩家是否应该掉落
     */
    private shouldPlayerFall(userId: string, playerX: number, playerY: number): boolean {
        // 如果玩家已经掉落，不重复检查
        if (this.fallenPlayers.has(userId)) {
            return false;
        }

        // 检查是否出了棋盘边界（任何情况下都执行）
        if (this.isPlayerOutOfBounds(playerX, playerY)) {

            return true;
        }

        // 只在掉落阶段(4)和补齐阶段(5)检查格子掉落
        if (this.currentGamePhase === 4 || this.currentGamePhase === 5) {
            if (!this.isPlayerOnSafeGround(playerX, playerY)) {

                return true;
            }
        }

        return false;
    }

    /**
     * 执行玩家掉落操作
     */
    private executePlayerFall(userId: string, options?: { forceOffset?: boolean; pixelX?: number; pixelY?: number; gridX?: number; gridY?: number }): void {


        // 标记玩家已掉落和正在掉落（位置锁定）
        this.fallenPlayers.add(userId);
        this.fallingPlayers.add(userId);

        // 获取玩家动物节点
        const animalNode = this.allPlayerAnimals.get(userId);
        if (!animalNode) {
            console.error(`找不到玩家 ${userId} 的动物节点`);
            return;
        }


        // 获取玩家数据
        const playerData = this.allPlayerData.get(userId);
        if (!playerData) {
            console.error(`找不到玩家 ${userId} 的数据`);
            return;
        }

        // 计算掉落偏移量（优先使用事件携带的权威像素坐标，保证各端方向一致）
        const baseX = (options && typeof options.pixelX === 'number') ? options.pixelX : animalNode.x;
        const baseY = (options && typeof options.pixelY === 'number') ? options.pixelY : animalNode.y;

        // 🔧 修复视觉不一致：如果服务器提供了权威坐标，先同步玩家位置到权威位置
        if (options && typeof options.pixelX === 'number' && typeof options.pixelY === 'number') {
            const currentX = animalNode.x;
            const currentY = animalNode.y;
            const deltaX = Math.abs(currentX - baseX);
            const deltaY = Math.abs(currentY - baseY);

            // 为了确保掉落动画的完全一致性，总是同步到服务器权威位置
            // 即使差异较小，也进行同步，确保动画起始位置精确
            if (deltaX > 1 || deltaY > 1) {
                console.log(`🔧 位置同步 - 玩家${userId}: 客户端(${currentX.toFixed(1)}, ${currentY.toFixed(1)}) -> 服务器(${baseX.toFixed(1)}, ${baseY.toFixed(1)})`);
                animalNode.x = baseX;
                animalNode.y = baseY;
            }
        }

        let fallOffset = { x: 0, y: 0 };

        // 🎯 完全按照后端返回的GridX、GridY来计算偏移
        if (options && typeof options.gridX === 'number' && typeof options.gridY === 'number') {
            const FALL_OFFSET_DISTANCE = 30;
            fallOffset = this.calculateGridCenterFallOffsetUniversal(baseX, baseY, options.gridX, options.gridY, FALL_OFFSET_DISTANCE);
            console.log(`🎯 使用后端GridX=${options.gridX}, GridY=${options.gridY} - 玩家位置: (${baseX.toFixed(1)}, ${baseY.toFixed(1)}), 偏移: (${fallOffset.x.toFixed(1)}, ${fallOffset.y.toFixed(1)})`);
        } else {
            // 如果没有后端GridX、GridY，则不做任何偏移（保持原位下落）
            fallOffset = { x: 0, y: 0 };
            console.log(`⚠️ 没有后端GridX/GridY参数，保持原位下落 - 玩家位置: (${baseX.toFixed(1)}, ${baseY.toFixed(1)})`);
        }


        // 【修复1】掉落动画触发前先隐藏bg节点
        this.hideBgNodeBeforeFall(animalNode, userId);

        // 播放掉落动画（带偏移）
        this.playFallAnimation(animalNode, playerData.playerType, fallOffset);

        // 如果是本地玩家，禁止摇杆输入并清零速度，避免掉落时仍可操作
        const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
        if (userId === myUserId) {
            this.isInputBlocked = true;
            this.currentPlayerVelocity = cc.Vec2.ZERO;
            if (this.joystickController) {
                this.joystickController.setInteractable(false);
            }
            // 显示GameOver节点（若为倒数第二个死亡，即场上仅剩1人生还，则不显示）
            const alive = this.getAlivePlayerCount();
            if (alive > 1) {
                this.showGameOver();
            }
        }
    }

    /**
     * 计算掉落偏移量
     * 根据掉落类型和位置确定动物预制体的偏移方向
     */
    private calculateFallOffset(playerX: number, playerY: number): { x: number, y: number } {
        const OFFSET_DISTANCE = 30; // 偏移距离（像素）

        // 检查是否是边界掉落
        if (this.isPlayerOutOfBounds(playerX, playerY)) {
            return this.calculateBoundaryFallOffset(playerX, playerY, OFFSET_DISTANCE);
        }

        // 格子掉落：在掉落阶段且不在安全地面上
        if ((this.currentGamePhase === 4 || this.currentGamePhase === 5) &&
            !this.isPlayerOnSafeGround(playerX, playerY)) {

            // 在GameEventFalling阶段，使用新的判断逻辑
            if (this.currentGamePhase === 4) {
                return this.calculateGridFallOffsetImproved(playerX, playerY, OFFSET_DISTANCE);
            } else {
                // 补齐阶段直接掉落

                return { x: 0, y: 0 };
            }
        }

        // 默认情况（不应该发生）

        return { x: 0, y: 0 };
    }

    /**
     * 计算边界掉落偏移量
     */
    private calculateBoundaryFallOffset(playerX: number, playerY: number, offsetDistance: number): { x: number, y: number } {
        const leftEdge = playerX - this.BOARD_CONFIG.PLAYER_SIZE * 0.25;
        const rightEdge = playerX + this.BOARD_CONFIG.PLAYER_SIZE * 0.25;
        const bottomEdge = playerY - this.BOARD_CONFIG.PLAYER_SIZE * 0.25;
        const topEdge = playerY + this.BOARD_CONFIG.PLAYER_SIZE * 0.25;

        let offsetX = 0;
        let offsetY = 0;

        // 检查各个边界（方向取反：上<->下，左<->右）
        if (leftEdge < this.BOARD_CONFIG.LEFT_X) {
            // 左边界出界，改为向左偏移
            offsetX = -offsetDistance;
        } else if (rightEdge > this.BOARD_CONFIG.RIGHT_X) {
            // 右边界出界，改为向右偏移
            offsetX = offsetDistance;
        }

        if (bottomEdge < this.BOARD_CONFIG.BOTTOM_Y) {
            // 下边界出界，改为向下偏移
            offsetY = -offsetDistance;
        } else if (topEdge > this.BOARD_CONFIG.TOP_Y) {
            // 上边界出界，改为向上偏移
            offsetY = offsetDistance;
        }


        return { x: offsetX, y: offsetY };
    }

    /**
     * 计算格子内掉落偏移量（朝致死格子中心偏移）
     * 用于处理玩家死亡时朝指定格子中心的偏移
     */
    private calculateGridCenterFallOffset(playerX: number, playerY: number, deathGridX: number, deathGridY: number, offsetDistance: number): { x: number, y: number } {
        // 获取致死格子的中心坐标
        const gridCenter = this.gridToPixel(deathGridX, deathGridY);

        // 计算玩家当前位置到格子中心的方向向量
        const dx = gridCenter.pixelX - playerX;
        const dy = gridCenter.pixelY - playerY;

        // 计算距离
        const distance = Math.sqrt(dx * dx + dy * dy);

        // 如果距离为0（玩家已在格子中心），则不偏移
        if (distance === 0) {
            return { x: 0, y: 0 };
        }

        // 归一化方向向量并应用偏移距离
        const normalizedX = dx / distance;
        const normalizedY = dy / distance;

        const offsetX = normalizedX * offsetDistance;
        const offsetY = normalizedY * offsetDistance;


        return { x: offsetX, y: offsetY };
    }

    /**
     * 通用格子中心偏移计算（支持出界格子）
     * 完全按照后端返回的GridX、GridY计算偏移，支持虚拟出界格子
     */
    private calculateGridCenterFallOffsetUniversal(playerX: number, playerY: number, gridX: number, gridY: number, offsetDistance: number): { x: number, y: number } {
        // 🎯 支持出界格子：如果GridX或GridY超出棋盘范围，虚拟创建格子
        // 计算虚拟格子的中心坐标（支持负数和超出范围的格子）
        const virtualGridCenterX = this.BOARD_CONFIG.LEFT_X + (gridX + 0.5) * this.BOARD_CONFIG.GRID_SIZE;
        const virtualGridCenterY = this.BOARD_CONFIG.BOTTOM_Y + (gridY + 0.5) * this.BOARD_CONFIG.GRID_SIZE;

        // 计算玩家当前位置到虚拟格子中心的方向向量
        const dx = virtualGridCenterX - playerX;
        const dy = virtualGridCenterY - playerY;

        // 计算距离
        const distance = Math.sqrt(dx * dx + dy * dy);

        // 如果距离为0，返回零偏移（避免除零错误）
        if (distance === 0) {
            console.log(`🎯 通用格子偏移 - 目标格子: (${gridX}, ${gridY}), 玩家已在格子中心，偏移: (0, 0)`);
            return { x: 0, y: 0 };
        }

        // 归一化方向向量并应用偏移距离
        const normalizedX = dx / distance;
        const normalizedY = dy / distance;
        const offsetX = normalizedX * offsetDistance;
        const offsetY = normalizedY * offsetDistance;

        // 判断是否为出界格子
        const isOutOfBounds = gridX < 0 || gridX >= this.BOARD_CONFIG.GRID_COUNT || gridY < 0 || gridY >= this.BOARD_CONFIG.GRID_COUNT;
        const boundaryType = isOutOfBounds ? "出界" : "棋盘内";

        console.log(`🎯 ${boundaryType}格子偏移 - 目标格子: (${gridX}, ${gridY}), 虚拟中心: (${virtualGridCenterX.toFixed(1)}, ${virtualGridCenterY.toFixed(1)}), 偏移: (${offsetX.toFixed(1)}, ${offsetY.toFixed(1)})`);

        return { x: offsetX, y: offsetY };
    }

    /**
     * 改进的格子掉落偏移计算（GameEventFalling阶段）
     * 使用更精确的算法区分两种情况
     */
    private calculateGridFallOffsetImproved(playerX: number, playerY: number, offsetDistance: number): { x: number, y: number } {
        // 获取玩家当前所在的主要格子坐标
        const playerGrid = this.pixelToGrid(playerX, playerY);

        // 计算玩家90%体积覆盖的所有格子
        const survivalSize = this.BOARD_CONFIG.PLAYER_SIZE * this.BOARD_CONFIG.SURVIVAL_RATIO;
        const halfSurvivalSize = survivalSize / 2;

        const leftX = playerX - halfSurvivalSize;
        const rightX = playerX + halfSurvivalSize;
        const bottomY = playerY - halfSurvivalSize;
        const topY = playerY + halfSurvivalSize;

        // 获取覆盖的格子范围
        const leftGrid = this.pixelToGrid(leftX, bottomY);
        const rightGrid = this.pixelToGrid(rightX, topY);

        const minGridX = Math.min(leftGrid.gridX, rightGrid.gridX);
        const maxGridX = Math.max(leftGrid.gridX, rightGrid.gridX);
        const minGridY = Math.min(leftGrid.gridY, rightGrid.gridY);
        const maxGridY = Math.max(leftGrid.gridY, rightGrid.gridY);

        // 统计覆盖的格子状态
        let totalCoveredGrids = 0;
        let fallenCoveredGrids = 0;
        let safeCoveredGrids = 0;

        for (let gridX = minGridX; gridX <= maxGridX; gridX++) {
            for (let gridY = minGridY; gridY <= maxGridY; gridY++) {
                if (gridX >= 0 && gridX < this.BOARD_CONFIG.GRID_COUNT &&
                    gridY >= 0 && gridY < this.BOARD_CONFIG.GRID_COUNT) {

                    // 检查这个格子与玩家90%体积是否有重叠
                    if (this.isGridOverlappingWithSurvivalArea(gridX, gridY, playerX, playerY, halfSurvivalSize)) {
                        totalCoveredGrids++;
                        const gridKey = `${gridX},${gridY}`;

                        if (this.fallenBlocks.has(gridKey)) {
                            fallenCoveredGrids++;
                        } else {
                            safeCoveredGrids++;
                        }
                    }
                }
            }
        }

        // 判断逻辑：
        // 1. 如果玩家覆盖的格子中，掉落格子占比 > 70% -> 站在掉落区域，不需要偏移
        // 2. 如果玩家覆盖的格子中，掉落格子占比 ≤ 70% -> 需要偏移

        const fallenRatio = totalCoveredGrids > 0 ? fallenCoveredGrids / totalCoveredGrids : 0;



        if (fallenRatio > 0.7) {
            // 超过70%的覆盖格子已掉落 -> 站在掉落区域，不需要偏移

            return { x: 0, y: 0 };
        } else {
            // 掉落格子占比不足70% -> 需要偏移

            return this.calculateOffsetTowardsFallenGrid(playerX, playerY, playerGrid, offsetDistance);
        }
    }

    /**
     * 计算格子掉落偏移量（GameEventFalling阶段）
     * 区分两种情况：站在格子上掉落 vs 走向已掉落的格子
     */
    private calculateGridFallOffset(playerX: number, playerY: number, offsetDistance: number): { x: number, y: number } {
        // 获取玩家当前所在的格子坐标
        const playerGrid = this.pixelToGrid(playerX, playerY);
        const currentGridKey = `${playerGrid.gridX},${playerGrid.gridY}`;

        // 检查当前格子是否已经在掉落列表中
        const isCurrentGridFallen = this.fallenBlocks.has(currentGridKey);

        // 检查玩家是否在安全地面上（这个方法会检查玩家90%体积覆盖的所有格子）
        const isOnSafeGround = this.isPlayerOnSafeGround(playerX, playerY);

        // 判断掉落类型的逻辑：
        // 1. 如果玩家不在安全地面上，且当前格子已掉落 -> 走向已掉落格子（需要偏移）
        // 2. 如果玩家不在安全地面上，但当前格子未掉落 -> 站在格子上一起掉落（不偏移）

        if (!isOnSafeGround && isCurrentGridFallen) {
            // 情况2：走向已掉落的格子区域 - 需要向掉落方向偏移

            return this.calculateOffsetTowardsFallenGrid(playerX, playerY, playerGrid, offsetDistance);
        } else {
            // 情况1：站在格子上时格子掉落，或者其他情况 - 不偏移，直接掉落

            return { x: 0, y: 0 };
        }
    }

    /**
     * 计算向已掉落格子方向的偏移
     * 玩家走向已经掉落的格子区域时，向该掉落区域偏移
     */
    private calculateOffsetTowardsFallenGrid(playerX: number, playerY: number, playerGrid: { gridX: number, gridY: number }, offsetDistance: number): { x: number, y: number } {
        // 计算当前格子的中心点
        const gridCenterX = this.BOARD_CONFIG.LEFT_X + (playerGrid.gridX + 0.5) * this.BOARD_CONFIG.GRID_SIZE;
        const gridCenterY = this.BOARD_CONFIG.BOTTOM_Y + (playerGrid.gridY + 0.5) * this.BOARD_CONFIG.GRID_SIZE;

        // 计算玩家相对于格子中心的偏移方向
        const dx = playerX - gridCenterX;
        const dy = playerY - gridCenterY;

        let offsetX = 0;
        let offsetY = 0;

        // 根据相对位置决定偏移方向（与之前相反：左->右，右->左，上->下，下->上）
        if (Math.abs(dx) > Math.abs(dy)) {
            // 水平方向偏移更明显 -> 方向取反：左改为右，右改为左
            if (dx < 0) {
                offsetX = offsetDistance; // 原左，改为向右
            } else {
                offsetX = -offsetDistance;  // 原右，改为向左
            }
        } else {
            // 垂直方向偏移更明显 -> 方向取反：上改为下，下改为上
            if (dy < 0) {
                offsetY = offsetDistance; // 原下，改为向上
            } else {
                offsetY = -offsetDistance;  // 原上，改为向下
            }
        }

        // 如果偏移量太小，使用默认的向下偏移（方向取反）
        if (offsetX === 0 && offsetY === 0) {
            offsetY = -offsetDistance; // 默认向下偏移
        }


        return { x: offsetX, y: offsetY };
    }

    /**
     * 从动物节点获取对应的用户ID
     */
    private getUserIdFromAnimalNode(animalNode: cc.Node): string | null {
        let foundUserId: string | null = null;
        this.allPlayerAnimals.forEach((node, userId) => {
            if (node === animalNode) {
                foundUserId = userId;
            }
        });
        return foundUserId;
    }

    /**
     * 播放动物掉落动画
     */
    private playFallAnimation(animalNode: cc.Node, playerType: string, fallOffset?: { x: number, y: number }): void {
        // 获取动物类型对应的动画名称
        const animalType = this.ANIMAL_TYPES[playerType];
        if (!animalType) {
            console.error(`未知的动物类型: ${playerType}`);
            return;
        }

        // 构造掉落动画名称: xialuo/动物类型
        const fallAnimationName = `xialuo/${animalType}`;

        // 查找dongwu节点
        const dongwuNode = animalNode.getChildByName('dongwu');
        if (!dongwuNode) {
            console.error(`找不到动物节点的dongwu子节点`);
            return;
        }

        // 获取骨骼动画组件
        const skeleton = dongwuNode.getComponent(sp.Skeleton);
        if (!skeleton) {
            console.error(`dongwu节点上没有骨骼动画组件`);
            return;
        }

        // 统一控制销毁时间：偏移动画时间(如果有) + 下落动画时间(0.5s) + 0.5s
        const hasOffset = !!(fallOffset && (fallOffset.x !== 0 || fallOffset.y !== 0));
        const offsetDurationSec = hasOffset ? 0.1 : 0.0; // 与下方 tween 保持一致
        const fallAnimDurationSec = 0.5; // 需求指定的下落动画时长
        const extraDelaySec = 0.2; // 额外缓冲
        const totalDelaySec = offsetDurationSec + fallAnimDurationSec + extraDelaySec;

        // 在总时长结束后，统一销毁节点（避免动画未播完就消失）
        this.scheduleOnce(() => {
            if (animalNode && cc.isValid(animalNode)) {
                // 销毁而非仅隐藏，避免残留
                animalNode.destroy();
            }

            // 解除位置锁定（从动物节点获取userId）
            const userId = this.getUserIdFromAnimalNode(animalNode);
            if (userId) {
                this.fallingPlayers.delete(userId);
                console.log(`🔓 位置锁定解除 - 玩家${userId}掉落动画完成`);
            }
        }, totalDelaySec);

        // 应用掉落偏移量
        if (hasOffset) {
            console.log(`🎬 播放掉落动画 - 偏移: (${fallOffset.x.toFixed(1)}, ${fallOffset.y.toFixed(1)})`);

            // 计算目标位置
            const originalX = animalNode.x;
            const originalY = animalNode.y;
            const targetX = originalX + fallOffset.x;
            const targetY = originalY + fallOffset.y;

            console.log(`🎬 掉落动画位置 - 起始: (${originalX.toFixed(1)}, ${originalY.toFixed(1)}) -> 目标: (${targetX.toFixed(1)}, ${targetY.toFixed(1)})`);

            // 先播放0.1秒的偏移移动动画
            cc.tween(animalNode)
                .to(offsetDurationSec, { x: targetX, y: targetY }, { easing: 'quadOut' })
                .call(() => {
                    // 偏移移动完成后，确保位置已更新
                    animalNode.x = targetX;
                    animalNode.y = targetY;


                    // 在新位置播放掉落动画
                    this.playFallAnimationInternal(skeleton, fallAnimationName, animalNode);
                })
                .start();
        } else {
            // 无偏移，直接在当前位置播放掉落动画

            this.playFallAnimationInternal(skeleton, fallAnimationName, animalNode);
        }
    }

    /**
     * 内部方法：播放掉落动画
     */
    private playFallAnimationInternal(skeleton: sp.Skeleton, fallAnimationName: string, animalNode: cc.Node): void {
        try {
            // 清除之前的监听器，防止冲突
            skeleton.setCompleteListener(null);

            // 播放掉落动画
            skeleton.setAnimation(0, fallAnimationName, false);

            // 设置下落动画完成监听器，播放死亡特效
            skeleton.setCompleteListener(() => {

                // 播放死亡音效（根据动物类型播放对应的音效）
                this.playDeathSoundEffect(animalNode);
                // 播放死亡特效（跟随动物节点当前位置，即偏移后的位置）
                this.playDeathEffect(animalNode);
            });

        } catch (error) {
            console.error(`播放掉落动画失败: ${fallAnimationName}`, error);
            // 如果动画播放失败，直接隐藏节点
            if (animalNode && cc.isValid(animalNode)) {
                animalNode.destroy();
            }
        }
    }

    /**
     * 更新掉落的方块信息
     */
    private updateFallenBlocks(blocks: BrickPartyBlock[]): void {
        if (!blocks) return;

        // 清空之前的掉落方块记录
        this.fallenBlocks.clear();

        // 添加新的掉落方块
        for (const block of blocks) {
            const gridKey = `${block.x},${block.y}`;
            this.fallenBlocks.add(gridKey);
        }


    }

    /**
     * 将大地图像素坐标转换为方块动画系统的格子坐标
     * 大地图坐标 -> 格子坐标 -> 方块动画系统坐标
     */
    private convertToBlockAnimationGrid(mapPixelX: number, mapPixelY: number): { gridX: number, gridY: number } {
        // 先转换为格子坐标
        const gridPos = this.pixelToGrid(mapPixelX, mapPixelY);

        // 格子坐标系统是一致的，直接返回
        return gridPos;
    }



    /**
     * 检查所有玩家的掉落状态
     */
    private checkAllPlayersFallStatus(): void {
        this.allPlayerAnimals.forEach((animalNode: cc.Node, userId: string) => {
            if (this.shouldPlayerFall(userId, animalNode.x, animalNode.y)) {
                this.executePlayerFall(userId);
            }
        });
    }

    /**
     * 实时掉落检测 - 在每个逻辑帧后执行
     */
    private performRealtimeFallDetection(): void {
        // 在掉落阶段（阶段4）期间持续进行完整的掉落检测
        if (this.currentGamePhase === 4) {
            // 掉落阶段：进行完整的掉落检测（边界 + 格子）
            this.allPlayerAnimals.forEach((animalNode: cc.Node, userId: string) => {
                if (this.shouldPlayerFall(userId, animalNode.x, animalNode.y)) {
                    if (!this.fallenPlayers.has(userId)) {

                        this.executePlayerFall(userId);
                    }
                }
            });
        } else {
            // 非掉落阶段只检查边界
            this.allPlayerAnimals.forEach((animalNode: cc.Node, userId: string) => {
                if (this.isPlayerOutOfBounds(animalNode.x, animalNode.y)) {
                    if (!this.fallenPlayers.has(userId)) {

                        this.executePlayerFall(userId);
                    }
                }
            });
        }
    }

    // ========== 掉落检测系统辅助方法 ==========

    /**
     * 重置掉落检测状态（游戏重新开始时调用）
     */
    public resetFallDetectionState(): void {
        this.fallenPlayers.clear();
        this.fallingPlayers.clear(); // 清理掉落中状态
        this.fallenBlocks.clear();
        this.currentGamePhase = 1;
        this.isInputBlocked = false;

        // 重置倒计时状态（但不隐藏倒计时节点，因为游戏开始后可能立即需要倒计时）
        this.isCountdownActive = false;
        this.isWaitingPhase = false;
        this.lastCountdownEventId = "";
        if (this.countdownTimer) {
            clearTimeout(this.countdownTimer);
            this.countdownTimer = 0;
        }
        // 注意：不在这里隐藏倒计时节点，让倒计时事件自己控制显示/隐藏

        // 清空地图背景
        this.clearMapBackgrounds();

        // 清理死亡特效节点
        this.clearDeathEffectNodes();

    }

    /**
     * 清空地图背景
     */
    private clearMapBackgrounds(): void {


        const backgroundNodes = ['game_bg', 'bg_up', 'bg_down'];
        backgroundNodes.forEach(nodeName => {
            const node = cc.find(nodeName);
            if (node) {
                const sprite = node.getComponent(cc.Sprite);
                if (sprite) {
                    sprite.spriteFrame = null;
                }
            } else {
                // 如果直接找不到，尝试在Canvas下查找
                const canvas = cc.find('Canvas');
                if (canvas) {
                    const foundNode = this.findNodeByName(canvas, nodeName);
                    if (foundNode) {
                        const sprite = foundNode.getComponent(cc.Sprite);
                        if (sprite) {
                            sprite.spriteFrame = null;
                        }
                    }
                }
            }
        });


    }

    /**
     * 递归查找节点
     */
    private findNodeByName(parent: cc.Node, name: string): cc.Node | null {
        if (parent.name === name) {
            return parent;
        }

        for (let child of parent.children) {
            const found = this.findNodeByName(child, name);
            if (found) {
                return found;
            }
        }

        return null;
    }

    /**
     * 清理视觉元素（退出游戏和结算时调用）
     */
    public cleanupVisualElements(): void {


        // 只恢复方块初始状态（清理动画状态），不清理动物预制体
        // 动物预制体（包括头像）应该保持显示，只在游戏重新开始时重新生成
        if (this.blockAnimationManager) {
            this.blockAnimationManager.stopAllAnimations();
            // 刷新方块引用，确保引用是最新的
            this.blockAnimationManager.refreshBlockReferences();
        }

        // 清理倒计时状态（退出游戏时隐藏倒计时，并移除动画完成回调）
        this.isCountdownActive = false;
        this.isWaitingPhase = false;
        this.lastCountdownEventId = "";
        if (this.countdownNode) {
            const sk = this.countdownNode.getComponent(sp.Skeleton);
            if (sk) {
                sk.setCompleteListener(null);
            }
            this.countdownNode.active = false;
        }

        // 清理视觉元素时也隐藏回合数显示
        this.hideRoundDisplay();

        // 重置回合数显示
        this.hideRoundDisplay();
        this.currentRound = 1;


    }

    /**
     * 完整的游戏状态重置（GameStart时调用）
     */
    public resetGameState(): void {


        // 1. 重置掉落检测状态
        this.resetFallDetectionState();

        // 2. 重置帧同步相关状态
        this.currentLogicFrame = 0;
        this.clientFrameId = 0; // 重置客户端帧ID
        this.lastBatchSendTime = Date.now();
        this.frameInputBuffer = [];
        this.isFrameInputStopped = false; // 重要：重置输入发送状态，允许第二把游戏发送输入

        // 重置摇杆相关状态
        this.isPlayerMoving = false;
        this.currentPlayerVelocity = cc.Vec2.ZERO;
        // 不在此处更改摇杆可交互状态，避免覆盖等待阶段(事件1)已设置的禁用

        // 2.1 清空上局的权威坐标/和解相关状态，避免把本地拉回上局死亡点
        this.lastServerPosition = null;
        this.isCollisionFollowing = false;
        this.isLocalFrozen = false;
        this.frozenLocalPos = cc.v2(0, 0);
        this.consecutiveCollisionFrames = 0;
        this.reconciliationHistory = [];
        this.lastReconciliationTime = 0;

        // 清理弱网缓冲/目标位置，避免使用上局的平滑缓冲
        this.playerPositionBuffers.clear && this.playerPositionBuffers.clear();
        this.targetPositions.clear();
        this.playerMovingStates.clear();

        // 3. 清理逻辑和渲染状态
        this.logicStates.clear();
        this.renderStates.clear();

        // 清理平滑移动系统状态
        this.reconciliationStates.clear();

        // 4. 重置网络抖动统计和网络质量评估
        this.networkJitterHistory = [];
        this.adaptiveRenderDelay = this.renderDelay;

        // 🎯 重置网络质量评估相关指标，确保新游戏从良好状态开始
        this.networkQuality = 'good';
        this.packetLossRate = 0;
        this.averageLatency = 0;
        this.latencyHistory = [];
        this.lastFrameReceiveTime = 0;
        this.totalPackets = 0;
        this.jitterSum = 0;
        this.jitterCount = 0;
        this.packetWindow = [];

        // 5. 重置所有方块到显示状态并清空动画（避免上一把隐藏状态残留）
        if (!this.blockAnimationManager) {
            this.initializeBlockAnimationManager();
        }
        if (this.blockAnimationManager) {
            this.blockAnimationManager.stopAllAnimations();
            this.blockAnimationManager.refreshBlockReferences();
        } else {
            console.warn('方块动画管理器未初始化，无法在重置时恢复方块可见性');
        }

        // 6. 彻底重置倒计时骨骼动画
        this.resetCountdownSkeletonAnimation();

        // 7. 重置jingbao系统状态
        this.resetJingbaoSystem();

        // 8. 重置GameOver显示
        this.hideGameOver();

        // 9. 重置回合数显示（避免上一把的回合数残留到新一把）
        this.hideRoundDisplay();
        this.currentRound = 1;

    }

    /**
     * 初始化坐标显示功能
     */
    private initializeCoordinateDisplay(): void {
        if (!this.coordinateLabel) {
            console.warn("坐标显示标签未设置，跳过坐标显示功能初始化");
            this.isCoordinateDisplayEnabled = false;
            return;
        }

        // 设置标签样式
        this.coordinateLabel.string = "坐标: (0, 0)";
        this.coordinateLabel.fontSize = 24;
        this.coordinateLabel.node.color = cc.Color.WHITE;

        // 设置标签位置（左上角）
        const canvas = cc.find("Canvas");
        if (canvas) {
            const canvasSize = canvas.getContentSize();
            this.coordinateLabel.node.setPosition(
                -canvasSize.width / 2 + 120,  // 距离左边120像素
                canvasSize.height / 2 - 50    // 距离顶部50像素
            );
        }


    }
    /**
   * 立即停止帧输入的采集和发送（断线时调用）
   * 同时隐藏摇杆，防止玩家在断线期间继续操作
   */
    public stopFrameInputImmediately(): void {
        this.isFrameInputStopped = true;
        this.frameInputBuffer = [];
        
        // 隐藏并禁用摇杆
        if (this.joystickController) {
            this.joystickController.setInteractable(false);
            console.log('[GamePageController] 断线检测，摇杆已禁用');
        }
        
        console.log('[GamePageController] 断线检测，立即停止帧输入发送');
    }

    // ========== 断线重连相关方法 ==========

    /**
     * 处理断线重连逻辑
     * 当EnterRoom响应中的roomId > 0时，表示这是断线重连
     * @param enterRoomData EnterRoom响应数据
     */
    public handleReconnection(enterRoomData: any): void {

        // 1. 判断是否为断线重连
        if (!this.isReconnection(enterRoomData)) {

            return;
        }

        // 标记重连流程中，避免不必要的启用操作（保留标识，便于后续扩展）
        this.isReconnecting = true;

        // 1.1 在恢复阶段之前，先根据 EnterRoom 的 users 预标记死亡玩家，
        // 确保后续 resume* 阶段中的 isLocalPlayerDead() 判断立刻正确，防止摇杆被短暂启用
        try {
            if (enterRoomData && Array.isArray(enterRoomData.users)) {
                this.premarkFallenFromEnterRoom(enterRoomData.users);
            }
        } catch (e) {
            console.warn('[Reconnection] 预标记死亡玩家失败:', e);
        }

        // 2. 清理当前场景
        this.cleanupSceneForReconnection();

        // 2.1 初始化本地客户端帧号，使断线重连后的 FrameInput 帧号从后端给定数字开始
        this.initializeClientFrameFromReconnection(enterRoomData);

        // 3. 根据消息重新生成场景元素
        this.regenerateSceneFromReconnectionData(enterRoomData);

        // 4. 根据gameStatus判断游戏阶段并执行相应逻辑
        this.resumeGameStateFromReconnection(enterRoomData);

        // 5. 最后的最后：确保玩家状态显示正确（延迟执行避免被其他逻辑覆盖）
        this.scheduleOnce(() => {

            this.handleReconnectionUserData(enterRoomData.users);
            // 在 EnterRoom 重连流程完成后，才恢复帧输入的采集与发送
            this.frameInputBuffer = []; // 清空残留缓冲
            this.lastBatchSendTime = Date.now(); // 对齐计时起点
            this.isFrameInputStopped = false; // 解除停止，允许继续发送 FrameInput
            // 重连流程结束
            this.isReconnecting = false;
        }, 0.1);

    }

    /**
     * 判断是否为断线重连
     * @param enterRoomData EnterRoom响应数据
     * @returns 是否为断线重连
     */
    private isReconnection(enterRoomData: any): boolean {
        // 根据API文档：roomId > 0 表示断线重连
        return enterRoomData && enterRoomData.roomId && enterRoomData.roomId > 0;
    }

    /**
     * 断线重连时清理场景
     * 清理背景、玩家头像预制体和动物预制体
     */
    private cleanupSceneForReconnection(): void {


        // 1. 清理所有动物预制体
        this.clearAllAnimals();

        // 2. 清理头像和分数界面
        if (this.gameLayoutController) {
            this.gameLayoutController.clearScoreView();
        }

        // 3. 重置游戏状态（但不完全重置，保留一些重连需要的状态）
        this.resetGameStateForReconnection();

        // 4. 清理方块动画状态
        if (this.blockAnimationManager) {
            this.blockAnimationManager.stopAllAnimations();
            this.blockAnimationManager.refreshBlockReferences();
        }


    }

    /**
     * 彻底重置倒计时骨骼动画状态
     */
    private resetCountdownSkeletonAnimation(): void {
        if (this.countdownNode) {
            const skeletonComponent = this.countdownNode.getComponent(sp.Skeleton);
            if (skeletonComponent) {
                try {
                    // 1. 停止当前所有正在播放的动画
                    skeletonComponent.clearTrack(0); // 清除轨道0的动画

                    // 2. 清除所有动画轨道，彻底移除已设置的动画
                    skeletonComponent.clearTracks();

                    // 3. 将骨骼恢复到初始姿势（绑定姿势），解决残留帧问题
                    skeletonComponent.setToSetupPose();

                    // 4. 清除完成监听器
                    skeletonComponent.setCompleteListener(null);


                } catch (error) {
                    console.error("❌ 重置倒计时骨骼动画失败:", error);
                }
            }

            // 隐藏节点
            this.countdownNode.active = false;
        }
    }

    /**
     * 为断线重连重置游戏状态（部分重置）
     */
    private resetGameStateForReconnection(): void {
        // 重置掉落检测相关状态：
        // 注意：不要在这里清空 fallenPlayers，避免在断线重连恢复阶段把“已死亡”的状态短暂清空，
        // 导致 resume* 阶段误判为存活从而启用摇杆，出现“显示→隐藏”的闪烁。
        // fallenPlayers 将在 handleReconnection() 中根据 EnterRoom.users 进行预标记与更新。
        // 保留已有的死亡集合，fallenBlocks 可以清空。
        this.fallenBlocks.clear();

        // 重置帧同步相关状态
        this.frameHistory.clear();
        this.frameInputBuffer = [];
        // 重连初期先停止发送，待初始化完成并收到权威帧后再恢复
        this.isFrameInputStopped = true;
        this.isReconnecting = true;

        // 重置摇杆状态
        this.isPlayerMoving = false;
        this.currentPlayerVelocity = cc.Vec2.ZERO;

        // 重置逻辑和渲染状态
        this.logicStates.clear();
        this.renderStates.clear();
        this.targetPositions.clear();
        this.playerMovingStates.clear();
        // 重置倒计时状态（不使用计时器，改为依赖骨骼动画完成事件）
        this.isCountdownActive = false;
        this.isWaitingPhase = false;
        this.lastCountdownEventId = "";

        // 彻底重置倒计时骨骼动画
        this.resetCountdownSkeletonAnimation();

        // 重置回合数显示
        this.hideRoundDisplay();
        this.currentRound = 1;

        // 重置输入阻塞状态
        this.isInputBlocked = false;
        this.isLocalFrozen = false;

        // 隐藏GameOver节点（重连时不应显示）
        this.hideGameOver();

        // 重置jingbao系统状态
        this.resetJingbaoSystem();
    }

    /**
     * 显示GameOver节点
     */
    private showGameOver(): void {
        if (this.gameoverNode) {
            this.gameoverNode.active = true;
        } else {
            console.warn("gameoverNode 未设置，无法显示GameOver");
        }
    }

    /**
     * 隐藏GameOver节点
     */
    private hideGameOver(): void {
        if (this.gameoverNode) {
            this.gameoverNode.active = false;
        }
    }

    /**
     * 根据断线重连数据重新生成场景元素
     * @param enterRoomData EnterRoom响应数据
     */
    private regenerateSceneFromReconnectionData(enterRoomData: any): void {


        // 1. 重新生成背景（根据mapType）
        this.regenerateBackground(enterRoomData.mapType);

        // 2. 重新生成头像和分数界面
        this.regenerateScoreView(enterRoomData.users);

        // 3. 重新生成动物预制体（按后端位置）
        this.regeneratePlayerAnimals(enterRoomData.users);

        // 4. 设置方块皮肤（在方块动画管理器初始化之后）
        this.setBlockSkinFromMapType();

        // 5. 场景重建完成（状态恢复将在handleReconnection方法最后延迟执行）

    }

    /**
     * 重新生成背景
     * @param mapType 地图类型（1-火山，2-冰川）
     */
    private regenerateBackground(mapType: number): void {


        // 通过全局单例设置背景图片，避免依赖节点名称
        const gm = getGlobalManager();
        if (gm) {
            gm.setMapBackground(mapType);

        } else {
            console.warn('❌ 未获取到GlobalManagerController实例，尝试临时查找Canvas内组件');
            const canvas = cc.find('Canvas');
            if (canvas) {
                const comp = canvas.getComponentInChildren('GlobalManagerController') as any;
                if (comp && typeof comp.setMapBackground === 'function') {
                    comp.setMapBackground(mapType);

                } else {
                    console.error('❌ 仍未找到GlobalManagerController或setMapBackground方法');
                }
            } else {
                console.error('❌ 未找到Canvas，无法设置背景');
            }
        }


    }

    /**
     * 重新生成头像和分数界面
     */
    private regenerateScoreView(users: any[]): void {


        if (this.gameLayoutController) {
            // 将users数据转换为PlayerData格式
            const playerDataArray: RoomUser[] = users.map(user => ({
                userId: user.userId,
                playerNumber: user.playerNumber || 1,
                x: user.x || 0,
                y: user.y || 0,
                playerType: user.playerType || 'red',
                surviveTime: 0,
                rank: 0,
                nickName: user.nickName,
                avatar: user.avatar,
                coin: user.coin,
                bpStatus: user.bpStatus,
                pos: user.pos,
                status: user.status,
                score: user.score
            }));

            // 临时设置gameStartData用于创建分数界面
            const tempGameStartData: NoticeStartGame = {
                roomId: 0,
                roomType: 1,
                playerNum: playerDataArray.length,
                mapType: 1,
                fee: 0,
                users: playerDataArray,
                gameStatus: 1,
                countDown: 0,
                propMode: 0,
                specialFull: 0,
                specialRemoved: 0,
                blockList: [],
                opIndex: 0,
                lookPos: 0,
                curTask: undefined
            };

            const originalGameStartData = GlobalBean.GetInstance().noticeStartGame;
            GlobalBean.GetInstance().noticeStartGame = tempGameStartData;

            // 创建分数界面
            this.gameLayoutController.createScoreView();

            // 恢复原始数据
            GlobalBean.GetInstance().noticeStartGame = originalGameStartData;
        }


    }

    /**
     * 重新生成动物预制体（按后端返回的位置）
     * @param users 玩家数据数组
     */
    private regeneratePlayerAnimals(users: any[]): void {

        const myUserId = GlobalBean.GetInstance().loginData?.userInfo?.userId;
        if (!myUserId) {
            console.error("无法获取玩家ID，无法重新生成动物预制体");
            return;
        }

        // 为每个玩家重新生成动物预制体
        for (const user of users) {
            // 🎯 计算保底位置：当坐标无效或落在默认中心(0,0)时，按座位号落到四角
            const isValidNum = (v: any) => typeof v === 'number' && !Number.isNaN(v) && Number.isFinite(v);
            let spawnX = isValidNum(user.x) ? user.x : Number.NaN;
            let spawnY = isValidNum(user.y) ? user.y : Number.NaN;
            
            const isCenter = (vx: number, vy: number) => isValidNum(vx) && isValidNum(vy) && Math.abs(vx) < 1e-3 && Math.abs(vy) < 1e-3;
            
            if (!isValidNum(spawnX) || !isValidNum(spawnY) || isCenter(spawnX, spawnY)) {
                // 使用服务端规范的默认出生点（单位：像素）
                const playerNumber = user.playerNumber || 1;
                switch (playerNumber) {
                    case 1: // 左上
                        spawnX = -240;
                        spawnY = 258;
                        break;
                    case 2: // 右上
                        spawnX = 240;
                        spawnY = 258;
                        break;
                    case 3: // 左下
                        spawnX = -240;
                        spawnY = -225;
                        break;
                    default: // 4或其他 -> 右下
                        spawnX = 240;
                        spawnY = -255;
                        break;
                }
                cc.warn(`[Reconnect Spawn] 无效或缺失坐标，使用默认出生点 userId=${user.userId}, playerNumber=${playerNumber}, fallback=(${spawnX.toFixed(1)}, ${spawnY.toFixed(1)})`, { x: user.x, y: user.y });
            } else {
                console.log(`[Reconnect Spawn] 使用服务器坐标 userId=${user.userId}, playerNumber=${user.playerNumber || 1}, position=(${spawnX.toFixed(1)}, ${spawnY.toFixed(1)})`);
            }
            
            // 存储玩家数据（使用计算后的保底位置）
            const playerData: RoomUser = {
                userId: user.userId,
                playerNumber: user.playerNumber || 1,
                x: spawnX,
                y: spawnY,
                playerType: user.playerType || 'red',
                surviveTime: 0,
                rank: 0,
                nickName: user.nickName,
                avatar: user.avatar,
                coin: user.coin,
                bpStatus: user.bpStatus,
                pos: 0,
                status: 0,
                score: 0
            };

            this.allPlayerData.set(user.userId, playerData);

            // 只为存活的玩家生成动物预制体
            if (user.bpStatus === 1) { // 1-存活，2-死亡
                // 生成动物预制体（使用计算后的保底位置）
                const animalNode = this.createAnimalPrefab(
                    user.playerType,
                    spawnX,
                    spawnY,
                    user.userId,
                    user.playerNumber
                );

                if (animalNode) {
                    // 存储动物节点
                    this.allPlayerAnimals.set(user.userId, animalNode);

                    // 如果是自己的动物，特别标记
                    if (user.userId === myUserId) {
                        this.myPlayerData = playerData;
                        this.myAnimalNode = animalNode;
                    }
                    
                    // 🔍 调试：验证动物节点创建后的实际位置
                    console.log(`[regeneratePlayerAnimals] 动物节点创建完成 userId=${user.userId}, 实际位置=(${animalNode.x.toFixed(1)}, ${animalNode.y.toFixed(1)})`);

                } else {
                    console.error(`❌ 为玩家 ${user.userId} 生成动物预制体失败`);
                }
            } else {

                // 将已死亡的玩家添加到掉落列表中
                this.fallenPlayers.add(user.userId);
            }
        }


    }

    /**
     * 根据gameStatus恢复游戏状态并执行相应逻辑
     * @param enterRoomData EnterRoom响应数据
     */
    private resumeGameStateFromReconnection(enterRoomData: any): void {


        const gameStatus = enterRoomData.gameStatus;
        const countDown = enterRoomData.countDown || 0;
        const currentBlocks = enterRoomData.currentBlocks;
        const currentRound = enterRoomData.currentRound || 1;


        // 更新当前游戏阶段
        this.currentGamePhase = gameStatus;

        // 根据不同的游戏状态执行相应逻辑
        switch (gameStatus) {
            case 1: // 等待阶段 - 游戏开始倒计时
                this.resumeWaitingPhase(countDown, currentRound);
                break;

            case 2: // 回合等待 - 补齐后等待下一回合
                this.resumeRoundWaitingPhase(countDown, currentRound);
                break;

            case 3: // 松动阶段 - 方块开始松动
                this.resumeShakingPhase(countDown, currentBlocks, currentRound);
                break;

            case 4: // 掉落阶段 - 方块开始掉落
                this.resumeFallingPhase(countDown, currentBlocks, currentRound);
                break;

            case 5: // 补齐阶段 - 方块补齐
                this.resumeRestoringPhase(countDown, currentBlocks, currentRound);
                break;

            case 6: // 结束阶段 - 游戏结束
                this.resumeEndingPhase();
                break;

            default:
                console.warn(`未知的游戏状态: ${gameStatus}`);
                break;
        }

        // 恢复帧同步状态
        if (enterRoomData.currentFrameId) {
            this.confirmedFrameId = enterRoomData.currentFrameId;
        }


    }

    /**
     * 恢复等待阶段
     */
    private resumeWaitingPhase(countDown: number, currentRound: number): void {


        this.isWaitingPhase = true;
        this.isLocalFrozen = true;
        
        // 🎯 修复断线重连位置被拉回(0,0)的问题：更新冻结位置为当前玩家位置
        if (this.myAnimalNode && cc.isValid(this.myAnimalNode)) {
            this.frozenLocalPos = cc.v2(this.myAnimalNode.x, this.myAnimalNode.y);
            console.log(`[resumeWaitingPhase] 更新冻结位置: (${this.frozenLocalPos.x.toFixed(1)}, ${this.frozenLocalPos.y.toFixed(1)})`);
        } else {
            this.frozenLocalPos = cc.v2(0, 0);
            console.warn(`[resumeWaitingPhase] 本地玩家节点不存在，使用默认冻结位置(0,0)`);
        }

        // 禁用摇杆交互
        if (this.joystickController) {
            this.joystickController.setInteractable(false);
        }

        // 断线重连时不播放倒计时动画，只设置状态

        // 断线重连时更新回合数显示（如果当前回合数大于0）
        if (currentRound > 0) {

            this.updateRoundDisplay(currentRound);
        }

    }

    /**
     * 恢复回合等待阶段
     */
    private resumeRoundWaitingPhase(countDown: number, currentRound: number): void {


        this.isWaitingPhase = true;

        // 启用摇杆交互（回合等待期间玩家可以移动）
        // 但如果本地玩家已死亡，则保持摇杆禁用状态
        if (this.joystickController && !this.isLocalPlayerDead()) {
            this.joystickController.setInteractable(true);
            // 摇杆启用时隐藏提示动画
            this.hideTishiAnimation();
        }

        // 断线重连时不播放倒计时动画，只设置状态

        // 断线重连时更新回合数显示
        if (currentRound > 0) {

            this.updateRoundDisplay(currentRound);
        }

    }

    /**
     * 恢复松动阶段
     */
    private resumeShakingPhase(countDown: number, currentBlocks: any[], currentRound: number): void {


        // 启用摇杆交互
        // 但如果本地玩家已死亡，则保持摇杆禁用状态
        if (this.joystickController && !this.isLocalPlayerDead()) {
            this.joystickController.setInteractable(true);
            // 摇杆启用时隐藏提示动画
            this.hideTishiAnimation();
        }

        // 断线重连时：拿到方块数据，直接播放方块抖动动画直到游戏状态切换
        if (currentBlocks && currentBlocks.length > 0) {

            this.resumeBlockShakeAnimation(currentBlocks);

            // 播放方块音效
            this.playBlockSoundEffect(3); // eventType: 3 - 松动阶段
        }

        // 断线重连时更新回合数显示
        if (currentRound > 0) {
            this.updateRoundDisplay(currentRound);
        }
    }

    /**
     * 恢复掉落阶段
     */
    private resumeFallingPhase(countDown: number, currentBlocks: any[], currentRound: number): void {


        // 启用摇杆交互
        // 但如果本地玩家已死亡，则保持摇杆禁用状态
        if (this.joystickController && !this.isLocalPlayerDead()) {
            this.joystickController.setInteractable(true);
            // 摇杆启用时隐藏提示动画
            this.hideTishiAnimation();
        }

        // 断线重连时：gameStatus==4时，直接隐藏格子，不播放动画
        if (currentBlocks && currentBlocks.length > 0) {

            this.updateFallenBlocks(currentBlocks);
            this.hideBlocksDirectly(currentBlocks);

            // 播放方块音效
            this.playBlockSoundEffect(4); // eventType: 4 - 掉落阶段
        }

        // 断线重连时更新回合数显示
        if (currentRound > 0) {
            this.updateRoundDisplay(currentRound);
        }
    }

    /**
     * 恢复补齐阶段
     */
    private resumeRestoringPhase(countDown: number, currentBlocks: any[], currentRound: number): void {

        // 启用摇杆交互
        // 但如果本地玩家已死亡，则保持摇杆禁用状态
        if (this.joystickController && !this.isLocalPlayerDead()) {
            this.joystickController.setInteractable(true);
            // 摇杆启用时隐藏提示动画
            this.hideTishiAnimation();
        }

        // 断线重连时：gameStatus==5时，显示格子，不做任何动画
        if (currentBlocks && currentBlocks.length > 0) {

            this.showBlocksDirectly(currentBlocks);

            // 播放方块音效
            this.playBlockSoundEffect(5); // eventType: 5 - 补齐阶段
        }

        // 断线重连时更新回合数显示
        if (currentRound > 0) {
            this.updateRoundDisplay(currentRound);
        }
    }

    /**
     * 恢复结束阶段
     */
    private resumeEndingPhase(): void {


        // 禁用摇杆交互
        if (this.joystickController) {
            this.joystickController.setInteractable(false);
        }

        // 停止帧输入发送
        this.isFrameInputStopped = true;
        this.isInputBlocked = true;

        // 游戏结束阶段隐藏回合数显示，防止带入下一局
        this.hideRoundDisplay();
    }

    /**
     * 恢复方块动画状态
     * @param blocks 方块数据
     * @param animationType 动画类型
     */
    private resumeBlockAnimation(blocks: any[], animationType: string): void {
        if (!this.blockAnimationManager || !blocks || blocks.length === 0) {
            return;
        }

        // 将方块数据转换为BrickPartyBlock格式
        const brickPartyBlocks: BrickPartyBlock[] = blocks.map((block, index) => ({
            id: index + 1,
            x: block.x || 0,
            y: block.y || 0,
            blockType: 1,
            animationState: this.getAnimationStateFromType(animationType),
            animationDelay: 0
        }));

        // 创建游戏事件数据
        const gameEventData: GameEventData = {
            eventType: this.getEventTypeFromAnimationType(animationType),
            currentRound: 1,
            countDown: 0,
            randomSeed: Math.floor(Math.random() * 100000),
            message: "",
            blocks: brickPartyBlocks
        };

        // 处理方块动画
        this.blockAnimationManager.handleGameEvent(gameEventData);
    }

    /**
     * 断线重连时恢复方块抖动动画（直接播放抖动动画直到游戏状态切换）
     * @param blocks 方块数据
     */
    private resumeBlockShakeAnimation(blocks: any[]): void {
        if (!this.blockAnimationManager || !blocks || blocks.length === 0) {
            return;
        }

        // 将方块数据转换为BrickPartyBlock格式
        const brickPartyBlocks: BrickPartyBlock[] = blocks.map((block, index) => ({
            id: index + 1,
            x: block.x || 0,
            y: block.y || 0,
            blockType: 1,
            animationState: BlockAnimationState.SHAKE,
            animationDelay: 0
        }));

        // 创建游戏事件数据
        const gameEventData: GameEventData = {
            eventType: 3, // 松动阶段
            currentRound: 1,
            countDown: 0,
            randomSeed: Math.floor(Math.random() * 100000),
            message: "",
            blocks: brickPartyBlocks
        };

        // 直接处理方块抖动动画，持续播放直到游戏状态切换
        this.blockAnimationManager.handleGameEvent(gameEventData);
    }

    /**
     * 断线重连时直接隐藏方块（不播放动画）
     * @param blocks 方块数据
     */
    private hideBlocksDirectly(blocks: any[]): void {
        if (!this.blockAnimationManager || !blocks || blocks.length === 0) {
            return;
        }

        // 直接隐藏指定的方块，不播放动画
        for (const block of blocks) {
            const gridX = block.x || 0;
            const gridY = block.y || 0;

            // 通过方块动画管理器直接隐藏方块
            this.blockAnimationManager.hideBlockDirectly(gridX, gridY);
        }


    }

    /**
     * 断线重连时直接显示方块（不播放动画）
     * @param blocks 方块数据
     */
    private showBlocksDirectly(blocks: any[]): void {
        if (!this.blockAnimationManager || !blocks || blocks.length === 0) {
            return;
        }

        // 直接显示指定的方块，不播放动画
        for (const block of blocks) {
            const gridX = block.x || 0;
            const gridY = block.y || 0;

            // 通过方块动画管理器直接显示方块
            this.blockAnimationManager.showBlockDirectly(gridX, gridY);
        }


    }

    /**
     * 根据动画类型获取动画状态
     */
    private getAnimationStateFromType(animationType: string): BlockAnimationState {
        switch (animationType) {
            case 'shake':
                return BlockAnimationState.SHAKE;
            case 'fall':
                return BlockAnimationState.FALL;
            case 'rise':
                return BlockAnimationState.RISE;
            default:
                return BlockAnimationState.NONE;
        }
    }

    /**
     * 根据动画类型获取事件类型
     */
    private getEventTypeFromAnimationType(animationType: string): number {
        switch (animationType) {
            case 'shake':
                return 3; // 松动阶段
            case 'fall':
                return 4; // 掉落阶段
            case 'rise':
                return 5; // 补齐阶段
            default:
                return 1; // 等待阶段
        }
    }

    /**
     * 开始倒计时显示
     * @param countDown 倒计时时间（毫秒）
     */
    private startCountdownDisplay(_countDown: number): void {
        // 统一为“收到消息时播放完整的321go动画，动画完成后自动隐藏”
        this.playCountdownAnimation();
    }

    /**
     * 播放方块音效
     * @param eventType 事件类型 (3-块晃动, 4-块下落, 5-块上升)
     */
    private playBlockSoundEffect(eventType: number): void {
        try {
            let soundName: string = "";

            switch (eventType) {
                case 3: // 松动阶段 - 块晃动
                    // 根据当前回合数选择不同时长的晃动音效
                    soundName = this.getShakeSoundByRound(this.currentRound);
                    break;
                case 4: // 掉落阶段 - 块下落
                    soundName = "block_down"; // 使用现有的块下落音效
                    break;
                case 5: // 补齐阶段 - 块上升
                    soundName = "block_up"; // 使用现有的块上升音效
                    break;
                default:
                    console.warn(`未知的方块事件类型: ${eventType}`);
                    return;
            }

            // 使用AudioManager播放音效，支持音效开关控制
            AudioManager.playSound(soundName);

        } catch (error) {
            console.error("播放方块音效失败:", error);
        }
    }

    /**
     * 根据回合区间返回对应的方块晃动音效名
     * - 1-10关: 2s 震动音效 -> block_move_2s
     * - 11-20关: 1.5s 震动音效 -> block_move_1_5s
     * - 21-30关: 1s 震动音效 -> block_move_1s
     * 超出范围时兜底为旧音效 "block_move"
     */
    private getShakeSoundByRound(roundNumber: number): string {
        const round = (typeof roundNumber === 'number' && roundNumber > 0) ? roundNumber : 1;
        if (round >= 1 && round <= 10) return "block_move2.0s";
        if (round >= 11 && round <= 20) return "block_move1.5s";
        if (round >= 21 && round <= 30) return "block_move1.0s";
        return "block_move";
    }

    /*
     * 播放321go音效
     */
    private play321GoSoundEffect(): void {
        try {
            // 使用AudioManager播放音效，支持音效开关控制
            AudioManager.playSound("321go");
        } catch (error) {
            console.error("播放321go音效失败:", error);
        }
    }

    /**
     * 播放死亡音效
     */
    private playDeathSoundEffect(animalNode?: cc.Node): void {
        try {
            // 默认音效
            let soundKey = "death";

            // 如果能定位到动物节点，尝试根据动物类型选择专属死亡音效
            if (animalNode) {
                const userId = this.getPlayerIdByAnimalNode(animalNode);
                if (userId) {
                    const pdata = this.allPlayerData.get(userId);
                    const pType = pdata && pdata.playerType;
                    // playerType 与动物映射：red→fox, yellow→kangraoo, green→parrot, pink→pig
                    if (typeof pType === "string") {
                        switch (pType) {
                            case "red":
                                soundKey = "death_fox"; break;
                            case "yellow":
                                // 命名遵循需求：death_kangraoo（注意拼写与资源一致）
                                soundKey = "death_kangraoo"; break;
                            case "green":
                                soundKey = "death_parrot"; break;
                            case "pink":
                                soundKey = "death_pig"; break;
                            default:
                                soundKey = "death"; break;
                        }
                    }
                }
            }

            // 使用AudioManager播放音效，支持音效开关控制
            AudioManager.playSound(soundKey);
        } catch (error) {
            console.error("播放死亡音效失败:", error);
        }
    }

    /**
     * 播放死亡特效
     * @param animalNode 动物节点
{{ ... }}
     */
    private playDeathEffect(animalNode: cc.Node): void {
        try {
            // 获取当前地图类型
            const mapType = this.getCurrentMapType();

            // 根据地图类型选择死亡特效动画
            let deathEffectName: string;
            if (mapType === 1) {
                // 地图1（火山）播放yanjiang特效
                deathEffectName = "yanjiang";
            } else if (mapType === 2) {
                // 地图2（冰川）播放shuihua特效
                deathEffectName = "shuihua";
            } else {
                // 默认使用shuihua特效
                deathEffectName = "shuihua";
                console.warn(`未知的地图类型: ${mapType}，使用默认死亡特效`);
            }



            // 创建死亡特效节点
            this.createDeathEffectNode(animalNode, deathEffectName);

        } catch (error) {
            console.error("播放死亡特效失败:", error);
        }
    }

    /**
     * 获取当前地图类型
     * @returns 地图类型（1-火山，2-冰川）
     */
    private getCurrentMapType(): number {
        try {
            // 从全局数据中获取地图类型
            const gameStartData = GlobalBean.GetInstance().noticeStartGame;
            if (gameStartData && gameStartData.mapType) {
                return gameStartData.mapType;
            }

            // 如果获取不到，默认返回地图1（火山）
            console.warn("无法获取地图类型，使用默认地图1（火山）");
            return 1;

        } catch (error) {
            console.error("获取地图类型失败:", error);
            return 1; // 默认返回地图1
        }
    }

    /**
     * 创建死亡特效节点
     * @param animalNode 动物节点
     * @param effectName 特效名称
     */
    private createDeathEffectNode(animalNode: cc.Node, effectName: string): void {
        try {
            // 如果没有死亡特效预制体，直接返回
            if (!this.deathEffectPrefab) {
                console.warn("死亡特效预制体未设置，无法播放死亡特效");
                return;
            }

            // 实例化死亡特效节点
            const deathEffectNode = cc.instantiate(this.deathEffectPrefab);
            if (!deathEffectNode) {
                console.error("创建死亡特效节点失败");
                return;
            }

            // 设置特效节点位置（跟随动物节点当前位置，即偏移后的位置）
            const currentPosition = animalNode.getPosition();
            deathEffectNode.setPosition(currentPosition);

            // 将特效节点添加到场景中
            animalNode.parent.addChild(deathEffectNode);

            // 获取玩家ID用于跟踪特效节点
            const userId = this.getPlayerIdByAnimalNode(animalNode);
            if (userId) {
                this.deathEffectNodes.set(userId, deathEffectNode);
            }

            // 获取骨骼动画组件
            const skeleton = deathEffectNode.getComponent(sp.Skeleton);
            if (!skeleton) {
                console.error("死亡特效节点上没有骨骼动画组件");
                deathEffectNode.destroy();
                if (userId) {
                    this.deathEffectNodes.delete(userId);
                }
                return;
            }

            // 播放死亡特效动画
            skeleton.setAnimation(0, effectName, false);

            // 设置动画完成监听器，动画播放完成后销毁节点
            skeleton.setCompleteListener(() => {

                if (deathEffectNode && cc.isValid(deathEffectNode)) {
                    deathEffectNode.destroy();
                }
                if (userId) {
                    this.deathEffectNodes.delete(userId);
                }
            });



        } catch (error) {
            console.error("创建死亡特效节点失败:", error);
        }
    }


    /**
     * 清理所有死亡特效节点
     */
    private clearDeathEffectNodes(): void {
        try {
            // 使用传统的forEach来避免迭代器兼容性问题
            this.deathEffectNodes.forEach((deathEffectNode, userId) => {
                if (deathEffectNode && cc.isValid(deathEffectNode)) {
                    deathEffectNode.destroy();
                }
            });
            this.deathEffectNodes.clear();

        } catch (error) {
            console.error("清理死亡特效节点失败:", error);
        }
    }

    // ========== 难度升级展示相关方法 ==========

    /**
     * 处理GameEventShowcase事件（难度升级展示阶段）
     * @param eventData 游戏事件数据
     */
    private handleShowcaseEvent(eventData: GameEventData): void {

        this.showJingbaoAnimation();
    }

    /**
     * 显示jingbao节点并播放难度升级动画
     */
    private showJingbaoAnimation(): void {
        if (!this.jingbaoNode) {
            console.warn("⚠️ jingbao节点未设置，无法播放难度升级动画");
            return;
        }



        // 显示节点
        this.jingbaoNode.active = true;
        this.jingbaoNode.zIndex = 10000; // 确保在最前层显示
        this.jingbaoNode.opacity = 255;

        // 播放警报音效（难度升级音效），与其他音效保持一致用法
        this.playWarningSoundEffect();
        // 获取骨骼动画组件
        const skeletonComponent = this.jingbaoNode.getComponent(sp.Skeleton);
        if (skeletonComponent) {
            try {
                // 播放难度升级动画
                skeletonComponent.setCompleteListener(null); // 清除旧的监听器
                skeletonComponent.setAnimation(0, "green", true); // 循环播放

            } catch (error) {
                console.error("❌ 播放难度升级动画失败:", error);
            }
        } else {
            console.warn("⚠️ jingbao节点未找到骨骼动画组件");
        }
    }

    /**
     * 隐藏jingbao节点并停止动画
     */
    private hideJingbaoAnimation(): void {
        if (!this.jingbaoNode) {
            return;
        }


        // 获取骨骼动画组件并停止动画
        const skeletonComponent = this.jingbaoNode.getComponent(sp.Skeleton);
        if (skeletonComponent) {
            try {
                // 停止动画并清除监听器
                skeletonComponent.clearTracks();
                skeletonComponent.setCompleteListener(null);
                skeletonComponent.setToSetupPose();
            } catch (error) {
                console.error("❌ 停止难度升级动画失败:", error);
            }
        }

        // 隐藏节点
        this.jingbaoNode.active = false;
    }

    /**
     * 播放难度升级的"warning"音效
     */
    private playWarningSoundEffect(): void {
        try {
            // 使用AudioManager播放音效，支持音效开关控制
            AudioManager.playSound("warning");
        } catch (error) {
            console.warn("播放warning音效失败:", error);
        }
    }

    /**
     * 初始化jingbao系统
     */
    private initializeJingbaoSystem(): void {
        if (!this.jingbaoNode) {
            console.warn("jingbao节点未设置，跳过jingbao系统初始化");
            return;
        }

        // 初始状态下隐藏jingbao节点
        this.jingbaoNode.active = false;

    }

    /**
     * 重置jingbao系统状态
     */
    private resetJingbaoSystem(): void {
        this.hideJingbaoAnimation();

    }

}
